/* =========================================================
   portfolio.css — Ada Voight Portfolio
   Mobile-first, earthy pastels, accessible
   ========================================================= */

/* ----- Design tokens ----- */
:root {
    --color-bg:           #FAFAF7;
    --color-text:         #3D3530;
    --color-text-muted:   #7A6E68;
    --color-accent-tan:   #C9B99A;
    --color-accent-sage:  #A8B5A2;
    --color-accent-rose:  #D4A5A5;
    --color-accent-terra: #C9A882;
    --color-accent-lav:   #B5A8C9;
    --color-accent-coast: #A8B5C9;

    /* Deeper earthy brown for interactive elements that need white text on
       top — the pastel accents above are too light to clear 4.5:1. This pair
       sits at ~4.9:1 and ~6.5:1 against white. */
    --color-accent-deep:       #8A6A45;
    --color-accent-deep-hover: #75593A;

    /* Muted terracotta for validation errors — reads as part of the palette
       rather than a browser-default red. ~4.8:1 on the white form panel. */
    --color-error:        #A85A3C;

    --color-nav-bg:       #F3EFE8;
    --color-nav-border:   #E0D8CC;
    --color-overlay-bg:   rgba(80, 72, 65, 0.82);

    --font-heading: 'Cormorant Garamond', 'Georgia', serif;
    --font-body:    'Nunito', 'Helvetica Neue', Arial, sans-serif;

    --transition-base: 0.3s ease;
    --radius-card:     8px;
    --shadow-card:     0 2px 12px rgba(80, 65, 50, 0.08);
    --shadow-hover:    0 6px 24px rgba(80, 65, 50, 0.16);
}

/* ----- Base ----- */
*, *::before, *::after {
    box-sizing: border-box;
}

/* No smooth scrolling on the root, and that is deliberate. It used to be, and
   it applied to every programmatic scroll on the page -- including the
   scrollTo(0, 0) Blazor makes after each enhanced navigation. That turned
   "go to the top of the new page" into an animation that began wherever the
   old page had been scrolled to: an artwork page painted part-way down and
   then slid up, and pressing Back fought the browser's own restoration. Set
   to auto explicitly so a later rule cannot quietly reintroduce it. If one
   place ever wants a glide, ask for it there with
   scrollIntoView({ behavior: "smooth" }), gated on prefers-reduced-motion. */
html {
    scroll-behavior: auto;
}

body {
    background-color: var(--color-bg);
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.65;
    margin: 0;
}

img, svg {
    display: block;
    max-width: 100%;
}

a {
    color: var(--color-text);
    text-decoration: none;
}

a:focus-visible {
    outline: 2px solid var(--color-accent-terra);
    outline-offset: 3px;
    border-radius: 2px;
}

/* ----- Skip link (accessibility) ----- */
.skip-link {
    position: absolute;
    top: -100%;
    left: 1rem;
    background: var(--color-accent-terra);
    color: white;
    padding: 0.5rem 1rem;
    border-radius: 0 0 4px 4px;
    font-weight: 600;
    z-index: 9999;
    transition: top 0.2s;
}
.skip-link:focus {
    top: 0;
}

/* =========================================================
   Navigation
   ========================================================= */
.portfolio-nav {
    position: sticky;
    top: 0;
    z-index: 100;
    background: var(--color-nav-bg);
    border-bottom: 1px solid var(--color-nav-border);
    backdrop-filter: blur(6px);
}

.nav-inner {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 1.25rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 56px;
}

.nav-brand {
    font-family: var(--font-heading);
    font-size: 1.4rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--color-text);
}

.nav-links {
    display: flex;
    gap: 1.5rem;
    list-style: none;
    margin: 0;
    padding: 0;
}

.nav-links a {
    font-size: 0.875rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    transition: color var(--transition-base);
}

.nav-links a:hover,
.nav-links a:focus-visible {
    color: var(--color-text);
}

/* =========================================================
   Hero / Banner
   ========================================================= */
.hero {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    text-align: center;
    /* Extra bottom padding reserves sepia space for the carousel's
       reflection + caption + dot indicators below the artwork band.
       Just enough clearance so the dots sit right up against the
       sepia/white boundary without touching it. */
    padding: 4rem 1.5rem 2rem;
    /* overflow hidden clips the decorative circles cleanly; the featured
       carousel is sized to sit safely within this band. */
    overflow: hidden;
    background: linear-gradient(
        160deg,
        #F3EDE2 0%,
        #EDE3D5 40%,
        #E8DDD0 100%
    );
}

/* Decorative soft circles */
.hero::before,
.hero::after {
    content: '';
    position: absolute;
    border-radius: 50%;
    pointer-events: none;
}
.hero::before {
    width: min(50vw, 420px);
    height: min(50vw, 420px);
    background: radial-gradient(circle, rgba(201,184,154,0.25) 0%, transparent 70%);
    top: -15%;
    right: -10%;
}
.hero::after {
    width: min(40vw, 340px);
    height: min(40vw, 340px);
    background: radial-gradient(circle, rgba(168,181,162,0.2) 0%, transparent 70%);
    bottom: -20%;
    left: -8%;
}

.hero-title {
    font-family: var(--font-heading);
    font-size: clamp(2.4rem, 7vw, 5rem);
    font-weight: 300;
    letter-spacing: 0.08em;
    color: var(--color-text);
    margin: 0 0 0.5rem;
    line-height: 1.1;
    position: relative;
    z-index: 1;
}

.hero-title:focus {
    outline: none;
}

.hero-subtitle {
    font-size: clamp(0.9rem, 2.5vw, 1.1rem);
    color: var(--color-text-muted);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    margin: 0;
    position: relative;
    z-index: 1;
}

.hero-divider {
    width: 60px;
    height: 1px;
    background: var(--color-accent-tan);
    margin: 1.5rem auto 0;
    position: relative;
    z-index: 1;
}

/* =========================================================
   Featured Carousel
   Sits inside the hero band: the centre piece, its two
   neighbours fanned out behind it, and a sliver of the next
   two behind those, each casting a shadow on the band.

   The geometry is a formula, not a set of states. Every slide
   carries its distance from the centre as a number (--k, from
   the class FeaturedCarousel gives it: 0 in the centre, 1 and
   -1 beside it, 2 and -2 peeking, 3 and beyond parked out of
   sight), and the rules under .featured-slide turn that number
   into a place, a size and an opacity. While a finger holds the
   carousel, portfolio.js adds one shared fraction (--drag, on
   the track) to every number at once, so the whole fan follows
   the hand through exactly the positions it would rest in.
   Change a slot here and the drag follows suit.

   A turn by arrow, dot or autoplay travels the same road: the
   two numbers are registered as real numbers below, so a change
   of class animates --k itself rather than the end values of
   transform and opacity, and every frame of a turn is a point
   the drag could have been at, the front handed over half way.
   Browsers without registered properties keep the plain
   transitions further down instead.
   ========================================================= */
@property --k {
    syntax: "<number>";
    inherits: false;
    initial-value: 4;
}

@property --drag {
    syntax: "<number>";
    inherits: true;
    initial-value: 0;
}

/* The width the fan is laid out around (see the stage, below). Registered so
   that a change to it glides: letting go of a drag would otherwise snap it
   from the blended width to the arriving piece's, and the whole fan with it. */
@property --cw0 {
    syntax: "<length>";
    inherits: true;
    initial-value: 0px;
}

.featured-carousel {
    /* The knobs. Everything below is drawn from these. */
    --stage-w: 460px;                    /* the widest a painting is drawn */
    --stage-h: 340px;                    /* the tallest */
    --beside: clamp(150px, 17.5vw, 205px); /* how far past the centre piece's edge a neighbour's outer edge sits */
    --sliver: 40px;                      /* how far past a neighbour's outer edge a sliver's sits */
    --veil: #EDE3D5;                     /* the band's colour, laid over pieces further back */
    --below: 48px;                       /* room under the centre painting for its cast shadow */
    --turn: 0.5s;                        /* how long a turn takes: by arrow, dot or autoplay, and the glide after a swipe */
    --drag: 0;                           /* written by portfolio.js while the carousel is held */
    --seam: 1;                           /* the dissolve along the front pair's seam, as a share of their overlap; 0 is a plain swap */
    --dip: 0;                            /* how far the front pair's opacity drops as they pass (0.3 = to 70%); 0 keeps them opaque */

    position: relative;
    z-index: 1;
    /* A sideways drag is the carousel's (portfolio.js); the browser keeps
       vertical panning for itself, so scrolling the page over it is unchanged. */
    touch-action: pan-y;
    /* Full width, so the arrows pinned to the section's edges sit out in the
       sepia gutters rather than against the artwork; the hero centres block
       children to their content width otherwise. The padding is the gutter. */
    width: 100%;
    max-width: 1100px;
    margin: 2.25rem auto 0;
    padding: 0 4.5rem;
    /* A mouse drag must not select the caption on its way past. */
    -webkit-user-select: none;
    user-select: none;
}

/* The stage: as tall as the tallest painting, so the band never changes
   height as pieces swap. Slides overflow it sideways on purpose; the hero
   clips them at its edge.

   The stage carries the centre piece's proportions (--active-ratio, set by
   FeaturedCarousel), so the lines the fan is laid out on can follow the
   centre piece's own edge: a narrow portrait in the middle pulls its
   neighbours in, a wide landscape pushes them out, and the gap beside the
   centre stays the same either way. While a finger holds the carousel,
   portfolio.js sets --cw0 on the track directly, blended from the width
   of the piece leaving the centre to the one arriving, so the lines move
   with the hand and are already where they will end up when it lets go. */
.featured-carousel-viewport {
    --cw0: min(var(--stage-w), calc(var(--stage-h) * var(--active-ratio, 1.333)));
    /* Whole pixels, as the canvas is, so the two say the same width. */
    --cw0: round(down, min(var(--stage-w), calc(var(--stage-h) * var(--active-ratio, 1.333))), 1px);
    position: relative;
    height: var(--stage-h);
    height: round(down, var(--stage-h), 1px);
}

.featured-carousel-track {
    --edge-1: calc(var(--cw0) / 2 + var(--beside));   /* the line the neighbours' outer edges sit on */
    --edge-2: calc(var(--edge-1) + var(--sliver));    /* the line the slivers' outer edges sit on */
}

.featured-carousel-track {
    position: relative;
    width: 100%;
    height: 100%;
}

/* ----- Where a slide goes, from its number -----
   --kk is the slide's distance from the centre with the drag added. --near
   is that clamped to one step either way; --far is what is left between one
   and two steps; --seg1/2/3 are how far into the first, second and third
   step the slide is, unsigned.

   Slides are placed by their OUTER edge, not their centre: a neighbour's
   outer edge lands on --edge-1 and a sliver's on --edge-2, whatever the
   painting's width -- so a narrow portrait two back peeks out past a wide
   neighbour by the same 40px a wide one would, and the neighbours' edges
   line up like a hand of cards. Each slide knows its own drawn width
   (--cw) from the proportions FeaturedCarousel sets on it as --ratio, and
   the translate solves for it: edge minus half the scaled width. The third
   step comes back to the middle: parked slides wait behind the centre
   piece, out of sight, so a piece entering the fan comes out from behind
   its neighbour rather than in from the wings. Size falls away step by
   step; the fade is the veil on the canvas, not opacity, so nothing shows
   through a piece to the one behind it. */
.featured-slide {
    --k: 4;
    --kk: calc(var(--k) + var(--drag));
    --near: clamp(-1, var(--kk), 1);
    --far: calc(clamp(-2, var(--kk), 2) - var(--near));
    --mag: max(var(--kk), calc(-1 * var(--kk)));
    --seg1: min(var(--mag), 1);
    --seg2: clamp(0, calc(var(--mag) - 1), 1);
    --seg3: clamp(0, calc(var(--mag) - 2), 1);
    --s: calc(1 - 0.22 * var(--seg1) - 0.12 * var(--seg2) - 0.06 * var(--seg3));
    --cw: min(var(--stage-w), calc(var(--stage-h) * var(--ratio, 1.333)));
    /* The scale the translate reckons the width at: the size a slide HAS at the
       end of its first step, throughout that step, so that a slide's travel over
       the step is a straight line and a finger dragging it stays with it. Past
       the first step it follows the real scale. */
    --sw: calc(0.78 - 0.12 * var(--seg2) - 0.06 * var(--seg3));

    /* ----- The seam -----
       Two pieces passing on the way in and out overlap by a strip -- the
       neighbour's width at side size, less --beside -- and at half a step the
       front changes hands, which would flip that strip from one painting to
       the other in a frame. Instead the piece on top (within half a step of
       the centre) is masked along its inner edge: transparent at the edge,
       opaque one strip in, so it dissolves into the opaque piece beneath.
       The mask grows as the square of the piece's progress to the midpoint
       and is exactly the strip there; --cw0 is the mean of the two widths at
       that moment, and the strip is in the piece's own pixels (hence / --s).
       The piece that takes the top has the mirror image, so what is drawn
       is the same the instant before and after the front changes hands:
       nothing pops, nothing outside the overlap is ever see-through, and
       both pieces are at full opacity throughout. Positional, like all the
       rest. --side says which edge is the inner one; the angle turns the
       gradient to start there. */
    --side: clamp(-1, calc(var(--kk) * 1000), 1);
    --top: clamp(0, calc((0.5 - var(--seg1)) * 1000 + 0.5), 1);
    --pass: clamp(0, calc(2 * var(--seg1)), 1);
    --feather: calc(var(--top) * var(--pass) * var(--pass) * var(--seam)
                    * (0.78 * var(--cw0) - var(--beside)) / var(--s));
    --presence: 1;                       /* how much of the slide is there: less only for two pieces, below */

    position: absolute;
    top: 0;
    left: 50%;
    width: var(--stage-w);
    height: var(--stage-h);
    transform-origin: 50% 50%;
    transform: translate3d(
                   calc(-50% + (var(--near) * var(--edge-1) + var(--far) * (var(--edge-2) - var(--edge-1)) - var(--near) * var(--cw) * var(--sw) / 2) * (1 - var(--seg3))),
                   0, 0)
               scale(var(--s));
    /* Opaque wherever it is; only parked pieces are gone. Nothing behind a
       piece is ever seen through it. Two pieces passing on the way in and out
       simply change places at half a step, as Swiper's coverflow does: a
       midpoint translucency tried here showed the band and the slivers
       through the front pair, and a veil in its place blanked them out. */
    opacity: calc(var(--presence) * (1 - var(--seg3)) * (1 - var(--dip) * 4 * var(--seg1) * (1 - var(--seg1))));
    filter: brightness(calc(1 - 0.06 * var(--seg1))) saturate(calc(1 - 0.12 * var(--seg1)));
    z-index: 1;
    pointer-events: none;
    transition: transform var(--turn) cubic-bezier(0.22, 0.61, 0.36, 1),
                opacity   0.6s ease,
                filter    0.6s ease;
    will-change: transform, opacity;
}

.featured-slide.is-active    { --k: 0;  z-index: 5; }
.featured-slide.is-next      { --k: 1;  z-index: 4; }
.featured-slide.is-prev      { --k: -1; z-index: 4; }
.featured-slide.is-far-next  { --k: 2;  z-index: 3; }
.featured-slide.is-far-prev  { --k: -2; z-index: 3; }
.featured-slide.is-deck-next { --k: 3; }
.featured-slide.is-deck-prev { --k: -3; }
.featured-slide.is-hidden    { --k: 4; }

/* ----- Two pieces -----
   FeaturedCarousel draws each of two pieces twice, so that a pull either
   way has a copy of the other to bring in (CarouselSlots.Slot), and this
   is what keeps each on screen once. Positional, like the rest: a slide
   is whole from half a step left of the centre to one step right of it,
   and fades out over the quarter step beyond either end. The two copies
   of a piece are always two steps apart, so one has gone by the time the
   other starts to appear: a piece is never seen in two places, not even
   faintly while it moves. At rest that is the centre piece and the other
   beside it on the right. A turn forward sends the centre piece left,
   fading once it has handed over the front -- so nothing is seen through
   the piece on top -- and then brings its copy in on the right. A pull to
   the right lets the other piece go on the right, then brings its
   left-hand copy in, whole by the time it takes the front. A copy
   changing sides crosses unseen, and the copies nobody sees at rest take
   no clicks.

   It once showed the other piece on both sides of the centre at once,
   which read as three paintings with one of them twice. Phones need none
   of this: nothing beside the centre is seen there at rest. */
.featured-carousel-viewport.is-pair .featured-slide {
    --presence: calc(clamp(0, calc(4 * var(--kk) + 3), 1) * clamp(0, calc(5 - 4 * var(--kk)), 1));
}

.featured-carousel-viewport.is-pair .featured-carousel-track .featured-slide.is-crossing {
    opacity: 0;
}

.featured-carousel-viewport.is-pair .featured-slide.is-prev .featured-slide-canvas,
.featured-carousel-viewport.is-pair .featured-slide.is-far-next .featured-slide-canvas {
    pointer-events: none;
}

/* Only the painting itself takes the pointer. The stage box around a
   narrow portrait is empty space, and a click there belongs to whatever
   is under it: an arrow, or a neighbour. The centre painting opens its
   page through the link around it; the neighbours and the slivers turn
   the carousel (see FeaturedCarousel.razor, SlideClick). */
.featured-slide.is-active .featured-slide-canvas,
.featured-slide.is-prev .featured-slide-canvas,
.featured-slide.is-next .featured-slide-canvas,
.featured-slide.is-far-prev .featured-slide-canvas,
.featured-slide.is-far-next .featured-slide-canvas {
    pointer-events: auto;
}

.featured-slide.is-prev .featured-slide-canvas,
.featured-slide.is-next .featured-slide-canvas,
.featured-slide.is-far-prev .featured-slide-canvas,
.featured-slide.is-far-next .featured-slide-canvas {
    cursor: pointer;
}

/* ----- While the carousel is held -----
   No transitions: the fan is wherever the hand is. */
.featured-carousel-track.is-dragging,
.featured-carousel-track.is-dragging .featured-slide,
.featured-carousel-track.is-dragging .featured-slide-canvas::before {
    transition: none;
}

.featured-carousel-track.is-dragging,
.featured-carousel-track.is-dragging .featured-slide-link,
.featured-carousel-track.is-dragging .featured-slide {
    cursor: grabbing;
}

/* ----- The same road for a turn as for a drag -----
   Where the browser registers custom properties (every current one; the
   feature test is a colour function that arrived in the same releases),
   the numbers animate and everything else is derived, so the transitions
   on transform and opacity above give way to one on --k and one on
   --drag. The two share a curve, which is what keeps a swipe's landing
   invisible: when the classes arrive, --k moves one way and --drag the
   other, in step, and their sum never changes. The front is handed over
   by the same number, at half a step, for drags and turns alike. */
@supports (color: rgb(from red r g b)) {
    .featured-carousel-track {
        transition: --drag var(--turn) cubic-bezier(0.22, 0.61, 0.36, 1),
                    --cw0 var(--turn) cubic-bezier(0.22, 0.61, 0.36, 1);
    }
    .featured-carousel-track .featured-slide {
        transition: --k var(--turn) cubic-bezier(0.22, 0.61, 0.36, 1);
        z-index: calc(5 - round(var(--mag), 1));
    }
    /* The veil follows the position too. Named through the track so this
       outranks the veil's own transition, declared later, which would
       otherwise have it chasing the fan with a lag. */
    .featured-carousel-track .featured-slide-canvas::before {
        transition: none;
    }
    /* A slide changing sides (marked by FeaturedCarousel) travels the same
       road as every other -- its number animates, so a drag and a turn move
       it alike -- but it must not become the centre piece on the way. So it
       grows no bigger than a neighbour, stays at the back, and is seen only
       near the slots: it fades out over the first fifth of the crossing, is
       gone for the middle three fifths, and fades in over the last fifth.
       All from its position, so the motion is one continuous glide -- and
       further out than a neighbour, where a swipe's landing finds it, it is
       whatever size it already is there, not snapped to a neighbour's. */
    .featured-carousel-track .featured-slide.is-crossing {
        --s: min(0.78, calc(1 - 0.22 * var(--seg1) - 0.12 * var(--seg2) - 0.06 * var(--seg3)));
        z-index: 2;
        opacity: calc(clamp(0, calc(2.5 * var(--mag) - 1.5), 1) * (1 - var(--seg3)));
    }
    .featured-carousel-track.is-dragging,
    .featured-carousel-track.is-dragging .featured-slide {
        transition: none;
    }
    @media (prefers-reduced-motion: reduce) {
        .featured-carousel-track {
            transition: --drag 0.2s ease, --cw0 0.2s ease;
        }
        .featured-carousel-track .featured-slide {
            transition: --k 0.2s ease;
        }
    }
}

/* Inner navigation link for the centre slide. The outer .featured-slide is
   always a <div> (so its element type stays stable across state changes and
   the transitions run); this <a> lives inside the centre slide only. */
.featured-slide-link {
    display: block;
    width: 100%;
    height: 100%;
    text-decoration: none;
    color: inherit;
}

/* The frame is the stage; the canvas inside it is the painting's own box,
   sized from the photograph's stored proportions (--ratio, set inline by
   FeaturedCarousel): as wide as the stage or as tall as it, whichever the
   proportions reach first. Standing on the frame's floor. */
.featured-slide-frame {
    position: relative;
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.featured-slide-canvas {
    position: relative;
    aspect-ratio: var(--ratio, 1.333);
    width: min(100%, calc(var(--stage-h) * var(--ratio, 1.333)));
    /* Whole pixels, where the browser can round: Safari on a phone draws a
       painting whose box ends on a fraction of a pixel one row short, and
       the band shows through as a hairline along the bottom edge. Browsers
       without round() keep the two lines above. */
    width: round(down, min(100%, calc(var(--stage-h) * var(--ratio, 1.333))), 1px);
    height: round(down, calc(min(var(--stage-w), calc(var(--stage-h) * var(--ratio, 1.333))) / var(--ratio, 1.333)), 1px);
}

/* The veil: the band's own colour laid over the painting, stronger the
   further back the piece sits. A piece further back is faded, not
   transparent -- the one behind it never shows through. */
.featured-slide-canvas::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 3;
    border-radius: 4px;
    background: var(--veil);
    opacity: calc(0.36 * var(--seg1) + 0.25 * var(--seg2));
    pointer-events: none;
    transition: opacity 0.6s ease;
}

/* The seam's mask (see --feather on the slide), on the painting and on its
   veil rather than on the canvas: Safari clips a masked element's shadows to
   its box, so the shadows stay unmasked, on the canvas. */
.featured-slide-image,
.featured-slide-canvas::before {
    -webkit-mask-image: linear-gradient(calc(var(--side) * 90deg), transparent 0, #000 var(--feather));
    mask-image: linear-gradient(calc(var(--side) * 90deg), transparent 0, #000 var(--feather));
}

.featured-slide-image {
    position: relative;
    z-index: 2;
    display: block;
    width: 100%;
    height: 100%;
    /* The box is the stored proportions, and the rendition's are the same
       to within a pixel of rounding, so the photograph simply fills the box:
       no object-fit, which on a phone's Safari could leave a row unpainted
       along the bottom, and at worst a pixel of stretch nobody can see. */
    border-radius: 4px;
}

/* On the canvas -- the painting's own box, pixel for pixel -- rather than
   on the painting: the seam's mask on the painting clips whatever the
   painting paints to that box, its shadow included. The hover lift is here
   with it, so the shadow still rises with the painting. */
.featured-slide-canvas {
    /* Surface-cast drop shadow — light coming from HIGH ABOVE, hitting
       an imaginary slightly-reflective surface below the painting.

       Design constraints:
         - Zero shadow bleed onto the image itself. The blur's upper edge
           (Y offset MINUS blur radius) must remain >= 0, so the softest
           part of the shadow starts at or below the image's bottom edge.
         - A high light source produces shadows cast FARTHER from the
           object, so the Y offset is generous — the shadow lives well
           beneath the painting, not hugging it.
         - Negative spread eats the sideways/upward wrap that would
           otherwise create the "floating in air" wrap-around look.

       Layer 1: soft mid-distance cast (higher Y so the top of its blur
                lands exactly at the image bottom edge — no upward bleed).
       Layer 2: farther, wider bloom — the diffuse spread of high light
                across the imaginary surface. */
    box-shadow: 0 18px 18px -8px  rgba(45, 32, 20, 0.55),
                0 40px 44px -12px rgba(45, 32, 20, 0.32);
    transition: transform var(--transition-base),
                box-shadow var(--transition-base);
}

/* Foreground piece: same directional character, but a heavier and slightly
   longer cast — the centre piece is nearer to the viewer than the side
   pieces, so its surface shadow is deeper and stretches further from the
   frame. Still no upward bleed onto the image itself. */
.featured-slide.is-active .featured-slide-canvas {
    box-shadow: 0 22px 22px -8px  rgba(45, 32, 20, 0.60),
                0 52px 56px -12px rgba(45, 32, 20, 0.40);
}

/* Ground shadow — a soft elliptical smudge on the imaginary surface
   directly beneath the painting. On the canvas, so it is as wide as the
   painting rather than as wide as the stage, and travels with the slide.
   box-shadow alone can't produce a true elliptical cast because it
   inherits the element's rectangular shape. */
.featured-slide-canvas::after {
    content: "";
    position: absolute;
    left: 50%;
    /* Pushed well BELOW the painting's bottom edge — a high light source
       casts the ground shadow farther from the object. Combined with the
       blur, the top of the shadow blob clears the image bottom by a
       comfortable margin (no darkening bleed onto the painting). */
    bottom: -26px;
    /* Slightly wider than the painting for the outward spread, and
       intentionally short so it reads as a flat cast on the surface
       rather than a floating blob. */
    width: 78%;
    height: 20px;
    transform: translateX(-50%);
    background: radial-gradient(ellipse at center,
                                rgba(45, 32, 20, 0.55) 0%,
                                rgba(45, 32, 20, 0.28) 40%,
                                rgba(45, 32, 20, 0) 75%);
    filter: blur(4px);
    border-radius: 50%;
    pointer-events: none;
    z-index: 0;
    transition: opacity var(--transition-base),
                width    var(--transition-base),
                height   var(--transition-base),
                bottom   var(--transition-base);
    opacity: 0.75;
}

/* Centre piece: bigger, darker, and dropped a bit further down since it
   sits closer to the viewer — reinforces "this one is closest, so it casts
   the deepest shadow onto the surface". */
.featured-slide.is-active .featured-slide-canvas::after {
    width: 84%;
    height: 24px;
    bottom: -32px;
    opacity: 1;
    background: radial-gradient(ellipse at center,
                                rgba(45, 32, 20, 0.65) 0%,
                                rgba(45, 32, 20, 0.32) 40%,
                                rgba(45, 32, 20, 0) 75%);
}

.featured-slide.is-active:hover .featured-slide-canvas,
.featured-slide.is-active:focus-visible .featured-slide-canvas,
.featured-slide-link:hover .featured-slide-canvas,
.featured-slide-link:focus-visible .featured-slide-canvas {
    /* Hover on the centre piece: the painting lifts 2px off the surface,
       so the surface-cast shadow drops FURTHER and grows a bit softer
       (further object = larger, more diffuse shadow footprint). Y offsets
       remain generous enough that the shadow's soft top edge never
       reaches the image bottom. */
    box-shadow: 0 28px 26px -8px  rgba(45, 32, 20, 0.55),
                0 60px 64px -12px rgba(45, 32, 20, 0.42);
    transform: translateY(-2px);
}

/* When the centre piece lifts on hover, its ground shadow expands, softens,
   and drops a little further — matches the "moved further from the surface"
   cue. */
.featured-slide.is-active:hover .featured-slide-canvas::after,
.featured-slide.is-active:focus-within .featured-slide-canvas::after {
    width: 92%;
    height: 28px;
    /* 36px below the painting on the surface: the canvas it hangs from is
       lifted 2px on hover, so 2px more to stay put. */
    bottom: -38px;
    background: radial-gradient(ellipse at center,
                                rgba(45, 32, 20, 0.55) 0%,
                                rgba(45, 32, 20, 0.28) 40%,
                                rgba(45, 32, 20, 0) 75%);
}

.featured-slide.is-prev:hover .featured-slide-canvas,
.featured-slide.is-next:hover .featured-slide-canvas {
    /* Hover on a neighbour: proportional lift with the same downward
       directionality. Still lighter than the centre piece so the depth
       hierarchy isn't inverted. */
    box-shadow: 0 22px 22px -8px  rgba(45, 32, 20, 0.50),
                0 46px 48px -12px rgba(45, 32, 20, 0.36);
    transform: translateY(-2px);
}

/* Ground shadow expands on neighbour hover too, but less than the centre
   piece so hierarchy stays consistent. */
.featured-slide.is-prev:hover .featured-slide-canvas::after,
.featured-slide.is-next:hover .featured-slide-canvas::after {
    width: 84%;
    height: 22px;
    bottom: -32px;   /* 30px on the surface; the canvas is lifted 2px */
    opacity: 0.9;
}

/* The reflection image is gone from the markup. A page rendered by an older
   build and styled by this sheet must not show it raw. */
.featured-slide-reflection {
    display: none;
}

/* ----- Caption band (below the stage) -----
   Rendered once, for the centre slide only, at a fixed position below the
   stage. The label sits at the same spot whichever painting is up. */
.featured-carousel-caption {
    text-align: center;
    line-height: 1.35;
    /* Room for the cast shadow, then a breath. */
    margin-top: calc(var(--below) + 0.75rem);
    /* Fixed min-height prevents layout shift as pieces with 1- vs
       2-word titles swap in and out. */
    min-height: 3.25rem;
}

.featured-carousel-caption-title {
    font-family: var(--font-heading);
    font-size: clamp(0.95rem, 1.9vw, 1.15rem);
    font-weight: 500;
    letter-spacing: 0.06em;
    color: var(--color-text);
    margin: 0;
}

.featured-carousel-caption-meta {
    font-size: 0.72rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    margin: 0.2rem 0 0;
}

.featured-slide-link:focus-visible {
    outline: none;
}

.featured-slide-link:focus-visible .featured-slide-image {
    outline: 2px solid var(--color-accent-terra);
    outline-offset: 4px;
}

/* ----- Prev / Next controls -----
   Minimalist: bare chevrons, no button chrome. The <button> element
   is kept for accessibility (@onclick, keyboard focus, aria-label) but
   its visual chrome — background, border, pill shape, blur — is stripped
   so all the viewer sees is the hairline arrow itself. Made deliberately
   large + full-opacity dark ink so it reads clearly against the sepia.

   HITBOX vs VISUAL: the button itself is a large, invisible strip that
   spans most of the stage height and ~30% of the section's width — so
   clicking anywhere on the left or right side of the carousel turns it.
   The visible chevron is positioned inside via padding + justify-content
   so it stays pinned near the outer edge. On a phone the neighbours peek
   out under these strips, and a tap on a peeking edge lands here: the
   same turn either way. */
.featured-carousel-nav {
    position: absolute;
    /* Vertically centered strip: 70% of the section height (15% inset
       from the top, 15% from the bottom). Keeps the "click this side"
       feel while pulling the hitbox away from the very top/bottom edges
       so it can't be triggered by stray cursor drift outside the artwork
       band. */
    top: 15%;
    height: 70%;
    /* ~30% of the carousel width per side. Leaves a comfortable central
       zone for the centre image / its <a> link so the two hit targets
       never overlap. */
    width: 30%;
    display: flex;
    /* Chevron sits at ~42% down — align items to the top and use
       padding-top to place it. */
    align-items: flex-start;
    background: transparent;
    border: none;
    padding: clamp(40px, 8vw, 90px) 0.5rem 0;
    color: var(--color-text);
    cursor: pointer;
    /* Below the centre slide (z-index 5), so the wide hitbox never steals
       clicks from its link even if their bounds overlap. Level with the
       neighbours (4), and later in DOM order, so it wins the click where
       they overlap — which is fine because both turn the same way. */
    z-index: 4;
    opacity: 0.75;
    transition: opacity var(--transition-base),
                transform var(--transition-base);
    /* Safari/iOS shows a translucent gray "tap highlight" rectangle over
       the entire tappable box on touch — normally unnoticeable on a small
       button, but glaring on this large invisible hitbox. Disable it
       explicitly rather than relying on the browser default. -webkit-
       appearance/user-select resets are cheap insurance against other
       WebKit button chrome (e.g. default padding/border look) leaking
       through since this is a real <button> element. */
    -webkit-tap-highlight-color: transparent;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-appearance: none;
    appearance: none;
    touch-action: manipulation;
}

.featured-carousel-nav:hover,
.featured-carousel-nav:focus-visible {
    opacity: 1;
    /* Warm sepia-consistent accent color on hover reinforces the
       "this is interactive" signal beyond just full opacity. */
    color: var(--color-accent-terra);
}

.featured-carousel-nav:focus-visible {
    outline: 2px solid var(--color-accent-terra);
    outline-offset: 4px;
    border-radius: 2px;
}

/* Pin the chevron horizontally: prev-arrow's SVG sits at the LEFT edge
   of its (now wide) hitbox; next-arrow's SVG sits at the RIGHT edge.
   That keeps the visible arrow glyph exactly where the viewer expects
   it while the invisible hitbox extends inward. */
.featured-carousel-prev { justify-content: flex-start; }
.featured-carousel-next { justify-content: flex-end; }

/* ----- Chevron hover animation -----
   Everything happens on the SVG so the hitbox (the outer button) stays
   put. Multi-property motion — drift + scale + thicker stroke + soft
   glow — reads clearly as an interactive prompt without adding button
   chrome. The prev/next chevrons drift TOWARD the direction they'll
   send the carousel, reinforcing the affordance. */
.featured-carousel-nav > svg {
    /* Slightly snappier easing for hover-in feel; still smooth enough
       to avoid feeling twitchy. drop-shadow on filter is animated so
       the glow blooms in with the hover, not just switches on. */
    transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1),
                filter    220ms ease,
                stroke-width 180ms ease;
    stroke-width: 2;
    /* transform-box: fill-box + transform-origin: center guarantees the
       scale-up pivots around the chevron's own middle rather than the
       SVG viewport's (0,0) corner — otherwise it would appear to drift
       diagonally instead of growing in place. */
    transform-box: fill-box;
    transform-origin: center;
}

.featured-carousel-nav:hover > svg,
.featured-carousel-nav:focus-visible > svg {
    stroke-width: 2.4;
    /* Warm, subtle glow keyed to the terra accent — matches the color
       shift above and gives the chevron a gentle "lift". Dialed to a
       softer bloom so the hover reads as refined rather than showy. */
    filter: drop-shadow(0 1px 2.5px rgba(160, 90, 55, 0.22));
}

.featured-carousel-prev:hover > svg,
.featured-carousel-prev:focus-visible > svg {
    transform: translateX(-5px) scale(1.08);
}

.featured-carousel-next:hover > svg,
.featured-carousel-next:focus-visible > svg {
    transform: translateX(5px) scale(1.08);
}

/* Press feedback: on active-click, dip the scale + drift a hair further
   so the chevron feels like it responds to the click, not just the hover. */
.featured-carousel-prev:active > svg {
    transform: translateX(-6px) scale(1.04);
}

.featured-carousel-next:active > svg {
    transform: translateX(6px) scale(1.04);
}

.featured-carousel-prev { left: 0; }
.featured-carousel-next { right: 0; }

/* ----- Dot indicators ----- */
.featured-carousel-dots {
    display: flex;
    justify-content: center;
    gap: 0.55rem;
    margin-top: 0.75rem;
}

.featured-carousel-dot {
    width: 7px;
    height: 7px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: var(--color-accent-tan);
    opacity: 0.4;
    cursor: pointer;
    transition: opacity var(--transition-base),
                transform var(--transition-base),
                background var(--transition-base);
}

.featured-carousel-dot:hover,
.featured-carousel-dot:focus-visible {
    opacity: 0.75;
}

.featured-carousel-dot.is-active {
    opacity: 1;
    background: var(--color-text);
    transform: scale(1.25);
}

.featured-carousel-dot:focus-visible {
    outline: 2px solid var(--color-accent-terra);
    outline-offset: 3px;
}

/* Phones: the painting is most of the screen's width and can be as tall
   as it is wide, and at rest it is alone -- a fan has no room on a screen
   this narrow, and edges crowding the gutters looked worse than nothing.
   The neighbours exist only in motion: a piece fades in from the edge as
   a finger pulls it in, or as a turn brings it, and fades out on its way
   out, so a drag still shows what is coming. */
@media (max-width: 640px) {
    .featured-carousel {
        --stage-w: min(80vw, 460px);
        --stage-h: min(78vw, 340px);
        --beside: 46vw;
        --sliver: 4vw;
        --below: 22px;
        /* Out to the screen's edges, past the hero's padding: the gutters
           either side of the painting are all the room the arrows have. */
        width: calc(100% + 3rem);
        max-width: none;
        margin: 1.75rem -1.5rem 0;
        padding: 0;
    }
    /* Whole in the centre, gone one step out, and every shade between while
       moving. Nothing beyond a neighbour is ever drawn. No seam: the pieces
       fade past each other here. */
    .featured-carousel {
        --seam: 0;
    }
    .featured-slide {
        opacity: calc(1 - var(--seg1));
    }
    /* Invisible pieces take no taps: the gutters belong to the arrows. */
    .featured-slide:not(.is-active) .featured-slide-canvas {
        pointer-events: none;
    }
    /* A crosser on a phone is never seen: both its slots are out of sight. */
    .featured-carousel-track .featured-slide.is-crossing {
        opacity: 0;
    }
    /* Exactly the gutter, and no wider: under the painting it would lose
       every tap to the painting's link. Centred on the stage. */
    .featured-carousel-nav {
        top: 0;
        height: var(--stage-h);
        width: 10vw;
        padding: 0;
        align-items: center;
        justify-content: center;
    }
}

/* Respect users who prefer reduced motion. */
@media (prefers-reduced-motion: reduce) {
    .featured-slide,
    .featured-slide-image {
        transition: opacity 0.2s ease;
    }
}

/* =========================================================
   Gallery
   ========================================================= */
.gallery-section {
    max-width: 1200px;
    margin: 0 auto;
    padding: 3rem 1.25rem 5rem;
}

/* The arrival fade. The three pages that had one -- .detail-page, .bio-page and
   .contact-page -- each hang it on their single wrapping element; the gallery has no
   such element and so never got one, which is the whole reason it was the odd page out.

   Opacity only, no translate. Those three are always arrived at from the top, so
   fadeInUp's 20px slide costs nothing there. The gallery can be arrived at part-way
   down by pressing Back, and an element moving while the browser restores the scroll
   position is the argument this file has already spent a long time settling.

   On the two sections rather than a wrapper around them, because Home has no wrapping
   element and adding one would be markup the page does not otherwise need. They are
   inserted in the same frame, so the two animations start together and read as one.

   Nothing here touches the card reveal: that is a transition on .artwork-card, which an
   ancestor's opacity does not affect. Opacity is also deliberate over transform for a
   second reason -- a transform on an ancestor becomes the containing block for
   position: fixed descendants, and the publish bar is one. */
.hero,
.gallery-section {
    animation: fadeIn 0.6s ease both;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}
.gallery-heading {
    font-family: var(--font-heading);
    font-size: clamp(1.1rem, 3vw, 1.4rem);
    font-weight: 400;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    text-align: center;
    margin: 0 0 2.5rem;
}

/* ----- Gallery header row: heading + filter -----
   The heading stays visually centered while the filter anchors to the
   right on wider screens. On narrow screens they stack. */
.gallery-header {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    margin: 0 0 2.5rem;
}

.gallery-header .gallery-heading {
    margin: 0;
}

@media (min-width: 640px) {
    .gallery-header {
        display: grid;
        grid-template-columns: 1fr auto 1fr;
        align-items: center;
        gap: 1rem;
    }
    .gallery-header .gallery-heading {
        grid-column: 2;
    }
    .gallery-header .gallery-filter {
        grid-column: 3;
        justify-self: end;
    }
}

/* ----- Filter dropdown ----- */
.gallery-filter {
    position: relative;
    font-family: var(--font-body);
}

.gallery-filter-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background: transparent;
    border: 1px solid var(--color-nav-border);
    border-radius: 999px;
    padding: 0.4rem 0.9rem;
    font-family: var(--font-body);
    font-size: 0.8rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    cursor: pointer;
    transition: color var(--transition-base),
                border-color var(--transition-base),
                background var(--transition-base);
}

.gallery-filter-toggle:hover,
.gallery-filter-toggle:focus-visible,
.gallery-filter-toggle.is-open {
    color: var(--color-text);
    border-color: var(--color-accent-tan);
    background: rgba(201, 184, 154, 0.12);
}

.gallery-filter-toggle:focus-visible {
    outline: 2px solid var(--color-accent-terra);
    outline-offset: 2px;
}

.gallery-filter-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 0.35rem;
    border-radius: 999px;
    background: var(--color-accent-terra);
    color: #FFF;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0;
}

.gallery-filter-chevron {
    transition: transform var(--transition-base);
}

.gallery-filter-toggle.is-open .gallery-filter-chevron {
    transform: rotate(180deg);
}

.gallery-filter-panel {
    position: absolute;
    top: calc(100% + 0.5rem);
    right: 0;
    min-width: 220px;
    background: var(--color-bg);
    border: 1px solid var(--color-nav-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-hover);
    padding: 0.75rem 0.5rem 0.5rem;
    z-index: 50;
    animation: filterPanelIn 0.18s ease both;
}

@keyframes filterPanelIn {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

.gallery-filter-group + .gallery-filter-group {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--color-nav-border);
}

.gallery-filter-group-heading {
    margin: 0.1rem 0.6rem 0.35rem;
    font-size: 0.68rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    font-weight: 600;
}

.gallery-filter-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.gallery-filter-option {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.6rem;
    border-radius: 4px;
    font-size: 0.9rem;
    color: var(--color-text);
    cursor: pointer;
    transition: background var(--transition-base);
}

.gallery-filter-option:hover,
.gallery-filter-option:focus-within {
    background: rgba(201, 184, 154, 0.18);
}

.gallery-filter-option input[type="checkbox"] {
    width: 1rem;
    height: 1rem;
    accent-color: var(--color-accent-terra);
    cursor: pointer;
}

.gallery-filter-option.is-selected {
    color: var(--color-text);
    font-weight: 600;
}

.gallery-filter-actions {
    display: flex;
    justify-content: flex-end;
    padding: 0.5rem 0.6rem 0.25rem;
    margin-top: 0.35rem;
    border-top: 1px solid var(--color-nav-border);
}

.gallery-filter-clear {
    background: transparent;
    border: none;
    color: var(--color-text-muted);
    font-family: var(--font-body);
    font-size: 0.78rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    cursor: pointer;
    padding: 0.25rem 0.4rem;
    border-radius: 4px;
    transition: color var(--transition-base), background var(--transition-base);
}

.gallery-filter-clear:hover:not(:disabled),
.gallery-filter-clear:focus-visible:not(:disabled) {
    color: var(--color-text);
    background: rgba(201, 184, 154, 0.18);
}

.gallery-filter-clear:disabled {
    opacity: 0.45;
    cursor: default;
}

.gallery-empty {
    text-align: center;
    color: var(--color-text-muted);
    font-style: italic;
    padding: 3rem 1rem;
    margin: 0;
}

/* ----- Masonry (Pinterest-style) gallery -----
   Uses CSS multi-column layout so thumbnails keep their natural aspect
   ratios (no cropping) while still tiling into a uniform-looking board.
   Items flow top-to-bottom within a column, then left-to-right — which
   preserves the order defined by ArtworkService. */
.gallery-grid {
    column-count: 1;
    column-gap: 1.5rem;
}

@media (min-width: 500px) {
    .gallery-grid {
        column-count: 2;
    }
}

@media (min-width: 900px) {
    .gallery-grid {
        column-count: 3;
        column-gap: 2rem;
    }
}

/* On very wide screens, cap the per-column width so thumbnails stay a
   consistent size and don't blow up to fill a 1200px container. */
@media (min-width: 1200px) {
    .gallery-grid {
        column-width: 350px;
        column-count: auto;
    }
}

/* ----- Artwork card ----- */
.artwork-card {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity 0.65s ease, transform 0.65s ease;

    /* Masonry essentials: prevent a card from being split across columns
       and add vertical spacing between cards within the same column. */
    break-inside: avoid;
    -webkit-column-break-inside: avoid;
    page-break-inside: avoid;
    margin-bottom: 1.5rem;
    display: block;
}

@media (min-width: 900px) {
    .artwork-card {
        margin-bottom: 2rem;
    }
}

.artwork-card.is-visible {
    opacity: 1;
    transform: translateY(0);
}

/* When revealed instantly (enhanced navigation), skip the transition */
.artwork-card.is-visible.no-transition {
    transition: none;
}

/* Staggered fade-in by column within the grid row.
   At the widest breakpoint there are 3 columns; this gives each card in a
   visible row a slight delay so they fade in left-to-right. */
.artwork-card:nth-child(3n + 1) { transition-delay: 0.05s; }
.artwork-card:nth-child(3n + 2) { transition-delay: 0.15s; }
.artwork-card:nth-child(3n)     { transition-delay: 0.25s; }

.artwork-card-link {
    display: block;
    border-radius: var(--radius-card);
    overflow: hidden;
    box-shadow: var(--shadow-card);
    transition: box-shadow var(--transition-base), transform var(--transition-base);
    background: white;
    text-decoration: none;
    color: inherit;
}

.artwork-card-link:hover,
.artwork-card-link:focus-visible {
    box-shadow: var(--shadow-hover);
    transform: translateY(-4px);
    outline: none;
}

/* Image + overlay container.
   No fixed aspect-ratio: each thumbnail keeps the painting's natural
   proportions so nothing is cropped. */
.artwork-image-wrap {
    position: relative;
    overflow: hidden;
    background: #EDE8E0;
    line-height: 0; /* removes the inline-baseline gap under <img> */
}

.artwork-image-wrap img,
.artwork-image-wrap svg {
    width: 100%;
    height: auto;
    display: block;
    transition: filter var(--transition-base);
}

.artwork-card-link:hover .artwork-image-wrap img,
.artwork-card-link:hover .artwork-image-wrap svg,
.artwork-card-link:focus-visible .artwork-image-wrap img,
.artwork-card-link:focus-visible .artwork-image-wrap svg {
    filter: grayscale(60%) brightness(0.8);
}

/* Overlay on hover */
.artwork-overlay {
    position: absolute;
    inset: 0;
    background: var(--color-overlay-bg);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    padding: 1.25rem;
    line-height: 1.4;
    opacity: 0;
    transition: opacity var(--transition-base);
}

.artwork-card-link:hover .artwork-overlay,
.artwork-card-link:focus-visible .artwork-overlay {
    opacity: 1;
}

.artwork-overlay-title {
    color: #F5F0EA;
    font-family: var(--font-heading);
    font-size: 1.1rem;
    font-weight: 600;
    text-align: center;
    margin: 0 0 0.4rem 0;
    letter-spacing: 0.03em;
}

.artwork-overlay-meta {
    color: #D8D0C8;
    font-size: 0.8rem;
    text-align: center;
    margin: 0;
    letter-spacing: 0.05em;
}

/* Card caption */
.artwork-caption {
    padding: 0.875rem 1rem 1rem;
}

.artwork-caption-title {
    font-family: var(--font-heading);
    font-size: 1.15rem;
    font-weight: 600;
    margin: 0 0 0.15rem;
    letter-spacing: 0.02em;
}

.artwork-caption-meta {
    font-size: 0.8rem;
    color: var(--color-text-muted);
    letter-spacing: 0.04em;
}

.artwork-accent-bar {
    height: 3px;
    border-radius: 0 0 var(--radius-card) var(--radius-card);
    transition: opacity var(--transition-base);
}

/* =========================================================
   Artwork Detail Page
   ========================================================= */
.detail-page {
    max-width: 860px;
    margin: 0 auto;
    padding: 2.5rem 1.25rem 5rem;
    animation: fadeInUp 0.6s ease both;
}

@keyframes fadeInUp {
    from { opacity: 0; transform: translateY(20px); }
    to   { opacity: 1; transform: translateY(0); }
}

.detail-back {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.85rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    margin-bottom: 2rem;
    transition: color var(--transition-base);
}
.detail-back:hover { color: var(--color-text); }
.detail-back svg  { flex-shrink: 0; }

.detail-image-wrap {
    border-radius: var(--radius-card);
    overflow: hidden;
    box-shadow: var(--shadow-hover);
    margin-bottom: 2rem;
    background: #EDE8E0;
    aspect-ratio: 4 / 3;
    max-height: 500px;
}

.detail-image-wrap img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    background: #EDE8E0;
}

.detail-accent-bar {
    height: 4px;
    border-radius: 2px;
    width: 50px;
    margin-bottom: 1.5rem;
}

.detail-title {
    font-family: var(--font-heading);
    font-size: clamp(2rem, 5vw, 3.2rem);
    font-weight: 400;
    line-height: 1.1;
    margin: 0 0 0.5rem;
    letter-spacing: 0.03em;
}

.detail-meta {
    font-size: 0.875rem;
    color: var(--color-text-muted);
    letter-spacing: 0.06em;
    margin-bottom: 1.75rem;
}

.detail-description {
    font-size: 1.05rem;
    line-height: 1.8;
    color: var(--color-text);
    max-width: 65ch;
}

/* =========================================================
   Bio / About page
   ========================================================= */
.bio-page {
    max-width: 1100px;
    margin: 0 auto;
    padding: 2.5rem 1.25rem 5rem;
    animation: fadeInUp 0.6s ease both;
}

/* ----- Hero ----- */
.bio-hero {
    margin-bottom: 2rem;
}

.bio-eyebrow {
    font-size: 0.8rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    margin: 0 0 0.75rem;
}

.bio-title {
    font-family: var(--font-heading);
    font-size: clamp(2.4rem, 6vw, 4rem);
    font-weight: 300;
    letter-spacing: 0.04em;
    line-height: 1.05;
    margin: 0 0 1rem;
    color: var(--color-text);
    outline: none;
}

.bio-lede {
    font-family: var(--font-heading);
    font-style: italic;
    font-size: clamp(1.05rem, 2.2vw, 1.3rem);
    color: var(--color-text-muted);
    line-height: 1.5;
    margin: 0;
    max-width: 28em;
}

.bio-divider {
    width: 60px;
    height: 1px;
    background: var(--color-accent-tan);
    margin: 1.5rem 0 0;
}

.bio-hero-image {
    max-width: 68ch;
    margin: 0 auto 3rem;
    border-radius: var(--radius-card);
    overflow: hidden;
    box-shadow: var(--shadow-hover);
    background: #EDE8E0;
}

.bio-hero-image img {
    width: 100%;
    height: auto;
    display: block;
}

/* ----- Prose body ----- */
.bio-prose {
    max-width: 68ch;
    margin: 0 auto;
    font-size: 1.05rem;
    line-height: 1.8;
    color: var(--color-text);
}

.bio-prose p {
    margin: 0 0 1.4rem;
}

/* ----- Pull-quote ----- */
.bio-pullquote {
    margin: 2.5rem 0;
    padding: 1.5rem 0 1.5rem 1.5rem;
    border-left: 3px solid var(--color-accent-tan);
}

.bio-pullquote p {
    font-family: var(--font-heading);
    font-style: italic;
    font-size: clamp(1.3rem, 3vw, 1.7rem);
    line-height: 1.4;
    color: var(--color-text);
    margin: 0;
}

/* ----- Inline figures ----- */
.bio-figure {
    margin: 2rem 0;
    border-radius: var(--radius-card);
    overflow: hidden;
    box-shadow: var(--shadow-card);
    background: #EDE8E0;
}

.bio-figure img {
    width: 100%;
    height: auto;
    display: block;
}

/* Wide image — full prose width */
.bio-figure-wide img {
    width: 100%;
    height: auto;
}

/* Float-right on wide screens, full-width on mobile */
@media (min-width: 720px) {
    .bio-figure-right {
        float: right;
        width: 42%;
        margin: 0.4rem 0 1.5rem 2rem;
    }
    .bio-figure-right img {
        height: auto;
    }

    .bio-figure-left {
        float: left;
        width: 42%;
        margin: 0.4rem 2rem 1.5rem 0;
    }
    .bio-figure-left img {
        height: auto;
    }
}

/* Clear floats after the prose section */
.bio-prose::after {
    content: '';
    display: block;
    clear: both;
}

/* =========================================================
   Contact page
   ========================================================= */

/* Note: no `overflow: hidden` here. The backdrop runs off the right edge and
   must be clipped, but clipping on this element would make it the scroll
   container for the sticky left rail below, which then never pins. The
   clipping lives on .contact-backdrop instead — an absolutely-positioned
   layer that is not an ancestor of the sticky element. */
.contact-page {
    position: relative;
    animation: fadeInUp 0.6s ease both;
}

/* ----- Hyde Park backdrop -----
   Offset off the top-right corner and faded almost to a whisper. The painting
   is cool blue while the rest of the site is warm sepia, so a light sepia +
   desaturation pass pulls it toward the site palette instead of fighting it.
   Masked on both axes so it dissolves rather than ending on a hard edge. */
/* Clipping layer: spans the page and hides the painting's overhang so the body
   never scrolls sideways. Being absolutely positioned and childless apart from
   the image, it can carry `overflow: hidden` without trapping anything else. */
.contact-backdrop {
    position: absolute;
    inset: 0;
    overflow: hidden;
    z-index: 0;
    pointer-events: none;
}

.contact-backdrop img {
    position: absolute;
    top: -6%;
    right: -14%;
    width: min(82%, 980px);
    height: auto;
    opacity: 0.42;
    /* A light sepia pass warms the painting's cool blues toward the site's
       earthy palette; the slight blur keeps it reading as atmosphere rather
       than as a second image competing with the gallery. */
    filter: sepia(0.22) saturate(0.78) brightness(1.04) blur(1.5px);
    /* Weighted to the upper right, where the opaque form panel sits beneath it.
       The left rail's muted body copy stays over clean paper, so the painting
       never costs the text any contrast. */
    -webkit-mask-image: radial-gradient(ellipse 78% 76% at 68% 34%,
                                        rgba(0, 0, 0, 1) 0%,
                                        rgba(0, 0, 0, 0.72) 42%,
                                        rgba(0, 0, 0, 0.22) 68%,
                                        rgba(0, 0, 0, 0) 85%);
            mask-image: radial-gradient(ellipse 78% 76% at 68% 34%,
                                        rgba(0, 0, 0, 1) 0%,
                                        rgba(0, 0, 0, 0.72) 42%,
                                        rgba(0, 0, 0, 0.22) 68%,
                                        rgba(0, 0, 0, 0) 85%);
}

/* Everything else stacks above the backdrop. */
.contact-inner {
    position: relative;
    z-index: 1;
    max-width: 1100px;
    margin: 0 auto;
    padding: 2.5rem 1.25rem 5rem;
}

/* ----- Hero ----- */
.contact-hero {
    margin-bottom: 2.75rem;
    max-width: 34em;
}

.contact-eyebrow {
    font-size: 0.8rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    margin: 0 0 0.75rem;
}

.contact-title {
    font-family: var(--font-heading);
    font-size: clamp(2.4rem, 6vw, 4rem);
    font-weight: 300;
    letter-spacing: 0.04em;
    line-height: 1.05;
    margin: 0 0 1rem;
    color: var(--color-text);
    outline: none;
}

.contact-lede {
    font-family: var(--font-heading);
    font-style: italic;
    font-size: clamp(1.05rem, 2.2vw, 1.3rem);
    color: var(--color-text-muted);
    line-height: 1.5;
    margin: 0;
}

.contact-divider {
    width: 60px;
    height: 1px;
    background: var(--color-accent-tan);
    margin: 1.5rem 0 0;
}

/* ----- Two-column layout ----- */
.contact-columns {
    display: grid;
    grid-template-columns: 1fr;
    gap: 2.5rem;
    align-items: start;
}

/* Single column: the form comes first. Source order puts the supporting copy
   ahead of it (which is the right reading order on desktop, where the two sit
   side by side), but stacked on a phone that buries the form a full screen
   down. Reordering visually rather than in markup keeps the desktop DOM order
   intact — and the form is the reason most visitors are on this page. */
.contact-form-panel {
    order: -1;
}

@media (min-width: 900px) {
    .contact-columns {
        /* Form gets the wider track — it is the working surface. */
        grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
        gap: 3.5rem;
    }

    /* Two columns: back to natural order, aside on the left. */
    .contact-form-panel {
        order: 0;
    }

    /* Stays alongside a long message while the visitor types. Offset clears
       the 56px sticky nav plus a little breathing room. */
    .contact-aside {
        position: sticky;
        top: 88px;
    }
}

/* ----- Left rail cards ----- */
.contact-aside {
    display: flex;
    flex-direction: column;
    gap: 1.75rem;
}

.contact-card {
    border-left: 2px solid var(--color-accent-tan);
    padding: 0.15rem 0 0.15rem 1.25rem;
}

.contact-card-title {
    font-family: var(--font-heading);
    font-size: 1.35rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    margin: 0 0 0.6rem;
    color: var(--color-text);
}

.contact-card-body {
    margin: 0;
    font-size: 0.95rem;
    line-height: 1.75;
    color: var(--color-text-muted);
}

.contact-card-note {
    margin: 0.6rem 0 0;
    font-size: 0.85rem;
    line-height: 1.6;
    color: var(--color-text-muted);
}
/* A contact card's detail that is neither an address nor a number: shown as words,
   in the same place and size a link would take. */
.contact-card-detail {
    margin: 0;
    font-family: var(--font-heading);
    font-size: 1.25rem;
    color: var(--color-text);
    word-break: break-word;
}

/* Live address, once one is configured. */
.contact-email-link {
    font-family: var(--font-heading);
    font-size: 1.25rem;
    color: var(--color-accent-deep);
    border-bottom: 1px solid rgba(138, 106, 69, 0.35);
    padding-bottom: 1px;
    transition: border-color var(--transition-base), color var(--transition-base);
    word-break: break-word;
}

.contact-email-link:hover {
    color: var(--color-accent-deep-hover);
    border-bottom-color: var(--color-accent-deep);
}

/* Placeholder state — rendered as plain text, never as a mailto, so nobody
   clicks through to an address that does not exist yet. */
.contact-email-placeholder {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin: 0;
}

.contact-email-placeholder-value {
    font-family: var(--font-heading);
    font-size: 1.25rem;
    color: var(--color-text-muted);
    border-bottom: 1px dashed var(--color-nav-border);
    padding-bottom: 1px;
    word-break: break-word;
}

/* Marks any copy on this page that is a stand-in awaiting Ada's own words. */
.contact-placeholder-badge {
    font-family: var(--font-body);
    font-size: 0.65rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    line-height: 1.4;
    color: var(--color-text-muted);
    background: var(--color-nav-bg);
    border: 1px solid var(--color-nav-border);
    border-radius: 100px;
    padding: 0.2rem 0.55rem;
    white-space: nowrap;
}

.contact-response-note {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    font-size: 0.85rem;
    color: var(--color-text-muted);
}

.contact-response-note svg {
    flex-shrink: 0;
}

/* ----- Form panel -----
   Sits on a near-opaque paper ground so the backdrop never competes with the
   text or the input borders. */
.contact-form-panel {
    background: rgba(255, 255, 255, 0.88);
    -webkit-backdrop-filter: blur(8px);
            backdrop-filter: blur(8px);
    border: 1px solid var(--color-nav-border);
    border-radius: var(--radius-card);
    padding: 2rem 1.75rem;
    box-shadow: var(--shadow-card);
}

@media (min-width: 560px) {
    .contact-form-panel {
        padding: 2.25rem;
    }
}

.contact-form-heading {
    font-family: var(--font-heading);
    font-size: 1.6rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    margin: 0 0 1.5rem;
    color: var(--color-text);
}

/* ----- Fields ----- */
.contact-field {
    margin-bottom: 1.25rem;
}

.contact-field label {
    display: block;
    font-size: 0.8rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    margin-bottom: 0.45rem;
}

.contact-required {
    color: var(--color-accent-deep);
}

.contact-optional {
    text-transform: none;
    letter-spacing: 0.02em;
    font-size: 0.78rem;
    font-style: italic;
    opacity: 0.75;
    margin-left: 0.25rem;
}

.contact-input {
    width: 100%;
    font-family: var(--font-body);
    font-size: 0.95rem;
    line-height: 1.5;
    color: var(--color-text);
    background: #FFFFFF;
    border: 1px solid var(--color-nav-border);
    border-radius: 5px;
    padding: 0.7rem 0.85rem;
    transition: border-color var(--transition-base), box-shadow var(--transition-base);
    -webkit-appearance: none;
            appearance: none;
}

.contact-input::placeholder {
    color: #B3A99F;
}

.contact-input:hover {
    border-color: var(--color-accent-tan);
}

.contact-input:focus,
.contact-input:focus-visible {
    outline: none;
    border-color: var(--color-accent-deep);
    /* A ring rather than an outline, so the rounded corners are followed. */
    box-shadow: 0 0 0 3px rgba(138, 106, 69, 0.16);
}

.contact-textarea {
    resize: vertical;
    min-height: 150px;
}

/* A native select needs its own chevron once appearance is stripped. */
.contact-select-wrap {
    position: relative;
}

.contact-select {
    padding-right: 2.4rem;
    cursor: pointer;
}

.contact-select-chevron {
    position: absolute;
    right: 0.9rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--color-text-muted);
    pointer-events: none;
}

/* Blazor stamps .invalid on fields that fail validation. */
.contact-input.invalid {
    border-color: var(--color-error);
}

.contact-input.invalid:focus {
    box-shadow: 0 0 0 3px rgba(168, 90, 60, 0.16);
}

.contact-validation {
    display: block;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--color-error);
    margin-top: 0.35rem;
}
/* How much of the message box is used, under its right-hand corner. */
.contact-count {
    margin: 0.35rem 0 0;
    text-align: right;
    font-size: 0.75rem;
    letter-spacing: 0.02em;
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
}

.contact-count.is-full {
    color: var(--color-error);
}

/* ----- Honeypot -----
   Pulled off-screen instead of display:none, since some bots skip fields that
   are explicitly hidden. aria-hidden + tabindex="-1" in the markup keep it away
   from keyboard and screen-reader users. */
.contact-honeypot {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* ----- Actions ----- */
.contact-actions {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    margin-top: 1.75rem;
}

@media (min-width: 560px) {
    .contact-actions {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: 1.25rem;
    }
}

.contact-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    font-family: var(--font-body);
    font-size: 0.85rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #FFFFFF;
    background: var(--color-accent-deep);
    border: 1px solid var(--color-accent-deep);
    border-radius: 5px;
    padding: 0.8rem 1.75rem;
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--transition-base),
                border-color var(--transition-base),
                transform 0.18s ease,
                box-shadow var(--transition-base);
    -webkit-tap-highlight-color: transparent;
}

.contact-button:hover:not(:disabled) {
    background: var(--color-accent-deep-hover);
    border-color: var(--color-accent-deep-hover);
    transform: translateY(-1px);
    box-shadow: 0 6px 16px rgba(80, 65, 50, 0.18);
}

.contact-button:active:not(:disabled) {
    transform: translateY(0);
    box-shadow: none;
}

.contact-button:focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 3px;
}

.contact-button:disabled {
    opacity: 0.65;
    cursor: default;
}

/* Secondary treatment for "send another message". */
.contact-button-quiet {
    color: var(--color-accent-deep);
    background: transparent;
    border-color: var(--color-nav-border);
}

.contact-button-quiet:hover:not(:disabled) {
    background: var(--color-nav-bg);
    border-color: var(--color-accent-tan);
    color: var(--color-accent-deep-hover);
    box-shadow: none;
}

.contact-spinner {
    width: 13px;
    height: 13px;
    border: 2px solid rgba(255, 255, 255, 0.4);
    border-top-color: #FFFFFF;
    border-radius: 50%;
    animation: contactSpin 0.7s linear infinite;
}

@keyframes contactSpin {
    to { transform: rotate(360deg); }
}

.contact-privacy-note {
    margin: 0;
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--color-text-muted);
}

/* ----- Alerts and confirmation ----- */
/* Room for the menu that sits at the top of the screen, so anything scrolled
   into view here -- the answer after Send, or a field taking focus -- is not
   tucked under it. */
.contact-success,
.contact-alert,
.contact-field {
    scroll-margin-top: 88px;
}

.contact-alert {
    border-left: 3px solid var(--color-error);
    background: rgba(168, 90, 60, 0.07);
    border-radius: 0 4px 4px 0;
    padding: 0.85rem 1rem;
    margin-bottom: 1.5rem;
    font-size: 0.9rem;
    line-height: 1.6;
    color: var(--color-text);
}

.contact-alert strong {
    display: block;
    margin-bottom: 0.15rem;
}

.contact-success {
    text-align: center;
    padding: 1.5rem 0.5rem;
    animation: fadeInUp 0.45s ease both;
}

.contact-success-mark {
    width: 54px;
    height: 54px;
    margin: 0 auto 1.25rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-accent-deep);
    background: var(--color-nav-bg);
    border: 1px solid var(--color-accent-tan);
}

.contact-success-title {
    font-family: var(--font-heading);
    font-size: clamp(1.4rem, 3vw, 1.75rem);
    font-weight: 600;
    line-height: 1.3;
    margin: 0 0 0.75rem;
    color: var(--color-text);
}

.contact-success-body {
    margin: 0 0 1.75rem;
    font-size: 0.95rem;
    line-height: 1.7;
    color: var(--color-text-muted);
}

.contact-success-body strong {
    color: var(--color-text);
    font-weight: 600;
    word-break: break-word;
}

/* ----- Narrow screens ----- */
@media (max-width: 640px) {
    /* Shrink and lift the backdrop so it stays a texture behind the heading
       rather than washing over the form. */
    .contact-backdrop img {
        top: -4%;
        right: -34%;
        width: 128%;
        opacity: 0.20;
    }

    .contact-inner {
        padding-bottom: 3.5rem;
    }
}

/* Respect reduced-motion preferences on this page's moving parts. */
@media (prefers-reduced-motion: reduce) {
    .contact-page,
    .contact-success {
        animation: none !important;
    }

    .contact-button {
        transition: none !important;
    }

    .contact-button:hover:not(:disabled) {
        transform: none;
    }

    .contact-spinner {
        animation-duration: 2s;
    }
}

/* =========================================================
   Not Found / Error pages
   ========================================================= */
.not-found-page,
.error-page {
    max-width: 600px;
    margin: 6rem auto;
    padding: 0 1.5rem;
    text-align: center;
}

.not-found-page h1,
.error-page h1 {
    font-family: var(--font-heading);
    font-size: 3rem;
    font-weight: 300;
    margin-bottom: 1rem;
}

/* =========================================================
   Footer
   ========================================================= */
.portfolio-footer {
    border-top: 1px solid var(--color-nav-border);
    /* The same wash as the nav bar, so the page is bookended top and bottom. */
    background: var(--color-nav-bg);
    padding: 0.9rem 1.25rem;
    text-align: center;
    font-size: 0.8rem;
    color: var(--color-text-muted);
    letter-spacing: 0.04em;
}

/* The site's only visible entry point into admin mode. Deliberately quiet —
   same size and weight as the copyright line — so a visitor's eye has no
   reason to land on it, while it stays a real, findable link rather than a
   hidden URL only the two admins happen to know. */
.portfolio-footer-separator {
    margin: 0 0.6em;
}

.portfolio-footer-admin-link {
    color: inherit;
    text-decoration: none;
    /* Underlines only the link text itself. The separator dot is a sibling
       element, not part of this box, specifically so the border below never
       stretches under it too. */
    border-bottom: 1px solid transparent;
    transition: border-color var(--transition-base), color var(--transition-base);
}

.portfolio-footer-admin-link:hover,
.portfolio-footer-admin-link:focus-visible {
    color: var(--color-text);
    border-color: currentColor;
}

.portfolio-footer-admin-link:focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 2px;
}

/* Which build is running. Deliberately quiet -- it is a diagnostic, and on a
   painter's site it should be findable rather than noticeable. Its own line so
   the copyright and sign-in row above reads as one thought. */
.portfolio-footer-version {
    display: block;
    width: 100%;
    margin-top: 0.5rem;
    font-size: 0.7rem;
    letter-spacing: 0.04em;
    color: var(--color-text-muted);
    opacity: 0.65;
}

/* =========================================================
   Utilities / Overrides
   ========================================================= */

/* Hide the default Blazor error UI gracefully */
#blazor-error-ui {
    background: #7A4830;
    color: #FFF;
    padding: 0.75rem 1.25rem;
    font-size: 0.875rem;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    display: none;
}

#blazor-error-ui[style*="display: block"],
#blazor-error-ui[style*="display:block"] {
    display: flex !important;
    align-items: center;
    gap: 1rem;
}

/* Reduce motion */
@media (prefers-reduced-motion: reduce) {
    .artwork-card,
    .artwork-card-link,
    .artwork-image-wrap img,
    .artwork-overlay {
        transition: none !important;
    }
    .artwork-card {
        opacity: 1 !important;
        transform: none !important;
    }
    .detail-page {
        animation: none !important;
    }
}

/* ------------------------------------------------------------------
   Admin bar
   Only rendered for a signed-in admin, so none of this CSS ever
   applies to an anonymous visitor's page. Sits above the sticky nav
   and pushes it down rather than overlapping it.
   ------------------------------------------------------------------ */

.admin-bar {
    position: sticky;
    top: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.5rem 1.25rem;
    background: var(--color-accent-deep);
    color: #FBF8F3;
    font-family: var(--font-body);
    font-size: 0.82rem;
}

.admin-bar-badge {
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-size: 0.68rem;
    padding: 0.15rem 0.5rem;
    border: 1px solid rgba(251, 248, 243, 0.45);
    border-radius: 2px;
}

.admin-bar-user {
    opacity: 0.85;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Pushes sign-out to the right without a spacer element. */
.admin-bar-signout-form {
    margin: 0 0 0 auto;
}

.admin-bar-signout {
    background: none;
    border: 1px solid rgba(251, 248, 243, 0.45);
    border-radius: 2px;
    color: inherit;
    font: inherit;
    padding: 0.15rem 0.6rem;
    cursor: pointer;
    transition: background var(--transition-base), border-color var(--transition-base);
}

.admin-bar-signout:hover {
    background: rgba(251, 248, 243, 0.14);
    border-color: rgba(251, 248, 243, 0.75);
}

.admin-bar-signout:focus-visible {
    outline: 2px solid #FBF8F3;
    outline-offset: 2px;
}

/* The nav is sticky at top:0 too; offset it so both stay visible. */
.admin-bar ~ .portfolio-nav {
    top: 2.15rem;
}

@media (max-width: 32rem) {
    .admin-bar-user { display: none; }
}

/* ------------------------------------------------------------------
   Publish bar and version history
   Rendered only inside a page's admin editing section, itself already
   behind <AdminOnly> — never reaches an anonymous visitor's page.
   ------------------------------------------------------------------ */

.publish-bar {
    position: fixed;
    left: 50%;
    bottom: 1.5rem;
    transform: translateX(-50%);
    z-index: 300;
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.75rem 1.25rem;
    background: var(--color-accent-deep);
    color: #FBF8F3;
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-hover);
    font-family: var(--font-body);
    font-size: 0.85rem;
    max-width: calc(100vw - 2rem);
}

.publish-bar-label {
    opacity: 0.9;
}

.publish-bar-actions {
    display: flex;
    gap: 0.6rem;
    flex-shrink: 0;
}

.publish-bar-discard,
.publish-bar-publish {
    font: inherit;
    font-weight: 600;
    border-radius: 4px;
    padding: 0.4rem 0.85rem;
    cursor: pointer;
    transition: background var(--transition-base), border-color var(--transition-base), opacity var(--transition-base);
    white-space: nowrap;
}

.publish-bar-discard {
    background: none;
    border: 1px solid rgba(251, 248, 243, 0.45);
    color: inherit;
}

.publish-bar-discard:hover:not(:disabled) {
    background: rgba(251, 248, 243, 0.14);
    border-color: rgba(251, 248, 243, 0.75);
}

.publish-bar-publish {
    background: #FBF8F3;
    border: 1px solid #FBF8F3;
    color: var(--color-accent-deep);
}

.publish-bar-publish:hover:not(:disabled) {
    background: #FFFFFF;
}

.publish-bar-discard:disabled,
.publish-bar-publish:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.publish-bar-discard:focus-visible,
.publish-bar-publish:focus-visible {
    outline: 2px solid #FBF8F3;
    outline-offset: 2px;
}

/* The bar is on screen the whole time now, not only once there is something to
   publish, so at rest it has to recede rather than sit there insisting. It comes
   back to full strength on hover or when anything inside it takes focus. */
.publish-bar.is-idle {
    opacity: 0.7;
}

.publish-bar.is-idle:hover,
.publish-bar.is-idle:focus-within {
    opacity: 1;
}

.publish-bar-confirm {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.6rem;
}

/* The question replaces the label rather than sitting beside it. Both say the same
   thing, and side by side the label lost the squeeze -- it rendered one word deep in
   a column an inch wide. */
.publish-bar.is-confirming .publish-bar-label {
    display: none;
}

.publish-bar-question {
    flex: 1 1 auto;
}

.publish-bar-discard.is-danger {
    border-color: rgba(255, 198, 184, 0.7);
    color: #FFDCD3;
}

.publish-bar-discard.is-danger:hover:not(:disabled) {
    background: rgba(255, 138, 112, 0.22);
    border-color: #FFDCD3;
}

.publish-bar-error {
    position: fixed;
    left: 50%;
    bottom: 5.5rem;
    transform: translateX(-50%);
    z-index: 300;
    background: var(--color-error);
    color: #FBF8F3;
    padding: 0.6rem 1rem;
    border-radius: var(--radius-card);
    font-size: 0.82rem;
    box-shadow: var(--shadow-card);
}

/* Version history: a small, unobtrusive dropdown rather than a second floating
   bar competing with the publish bar for attention. */
.version-history {
    position: relative;
    margin: 1rem auto;
    max-width: 1200px;
    padding: 0 1.25rem;
    font-family: var(--font-body);
    font-size: 0.82rem;
}

.version-history-toggle {
    background: none;
    border: 1px solid var(--color-nav-border);
    border-radius: 4px;
    color: var(--color-text-muted);
    font: inherit;
    padding: 0.35rem 0.75rem;
    cursor: pointer;
    transition: border-color var(--transition-base), color var(--transition-base);
}

.version-history-toggle:hover {
    color: var(--color-text);
    border-color: var(--color-text-muted);
}

.version-history-list {
    list-style: none;
    margin: 0.5rem 0 0;
    padding: 0;
    border: 1px solid var(--color-nav-border);
    border-radius: var(--radius-card);
    background: var(--color-bg);
    /* Widened from 26rem when each row gained a bin: at the old width a row with a
       date, an email, Restore and the bin wrapped the bin onto a line of its own. */
    max-width: 30rem;
    overflow: hidden;
}

.version-history-item {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem;
    padding: 0.55rem 0.85rem;
    border-top: 1px solid var(--color-nav-border);
}

.version-history-item:first-child {
    border-top: none;
}

.version-history-item.is-current {
    background: rgba(168, 181, 162, 0.12);
}

.version-history-number {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--color-text);
}

/* A real basis, not 1 1 0: with a zero basis this collapsed to the width of its
   longest word and rendered a date one word per line down an inch-wide column. */
.version-history-date {
    color: var(--color-text-muted);
    flex: 1 1 9rem;
}

.version-history-by {
    color: var(--color-text-muted);
    font-size: 0.76rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 9rem;
}

.version-history-current-tag {
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-accent-sage);
}

.version-history-restore {
    background: none;
    border: 1px solid var(--color-nav-border);
    border-radius: 4px;
    color: var(--color-text);
    font: inherit;
    font-size: 0.76rem;
    padding: 0.25rem 0.55rem;
    cursor: pointer;
    transition: border-color var(--transition-base), background var(--transition-base);
    white-space: nowrap;
}

.version-history-restore:hover:not(:disabled) {
    background: var(--color-nav-bg);
    border-color: var(--color-text-muted);
}

.version-history-restore:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Destructive twin of the restore button, for confirming a version deletion. Red
   only once the confirmation is on screen -- the bin itself is quiet, because the
   click that arms it costs nothing. */
.version-history-restore.is-danger {
    border-color: rgba(168, 90, 60, 0.4);
    color: var(--color-error);
}

.version-history-restore.is-danger:hover:not(:disabled) {
    background: var(--color-error);
    color: var(--color-bg);
}

.version-history-confirm {
    flex: 1 1 12rem;
    color: var(--color-text);
}

.version-history-delete {
    background: none;
    border: 1px solid transparent;
    border-radius: 4px;
    color: var(--color-text-muted);
    padding: 0.25rem 0.3rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    transition: color var(--transition-base), border-color var(--transition-base);
}

.version-history-delete:hover:not(:disabled) {
    color: var(--color-error);
    border-color: var(--color-nav-border);
}

.version-history-delete:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.version-history-delete:focus-visible {
    outline: 2px solid var(--color-error);
    outline-offset: 1px;
}

@media (max-width: 32rem) {
    .publish-bar {
        left: 1rem;
        right: 1rem;
        bottom: 1rem;
        transform: none;
        flex-direction: column;
        align-items: stretch;
        max-width: none;
    }

    .publish-bar-actions {
        justify-content: flex-end;
    }

    /* The buttons take their own line under the question, which is what stops the
       confirmation running off the side of a phone. */
    .publish-bar-question {
        flex-basis: 100%;
    }

    /* Who published it takes its own line, leaving the first one to the version
       number, the date and the two controls. */
    .version-history-by {
        flex-basis: 100%;
        max-width: none;
    }
}

/* ------------------------------------------------------------------
   Artwork editing panel and the deleted-pieces page
   Admin-only surfaces: rendered inside <AdminOnly> or behind an
   [Authorize] route, so none of this reaches a visitor's page.
   ------------------------------------------------------------------ */

.artwork-edit {
    max-width: 800px;
    margin: 3rem auto 6rem;
    /* The publish bar is fixed to the bottom of the viewport and, since it became
       permanent rather than appearing only once there were changes, it now covers
       that strip at every scroll position. The extra bottom padding is what the
       version history list scrolls clear into. */
    padding: 1.5rem 1.25rem 5.5rem;
    border: 1px solid var(--color-nav-border);
    border-radius: var(--radius-card);
    background: var(--color-nav-bg);
    font-family: var(--font-body);
}

.artwork-edit-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

.artwork-edit-heading {
    font-family: var(--font-heading);
    font-size: 1.4rem;
    font-weight: 400;
    margin: 0;
    color: var(--color-text);
}

.artwork-edit-slug {
    font-size: 0.78rem;
    color: var(--color-text-muted);
    background: var(--color-bg);
    border: 1px solid var(--color-nav-border);
    border-radius: 3px;
    padding: 0.15rem 0.45rem;
}

.artwork-edit-loading,
.admin-page-empty {
    max-width: 800px;
    margin: 2rem auto;
    padding: 0 1.25rem;
    color: var(--color-text-muted);
    font-family: var(--font-body);
    font-size: 0.9rem;
}

.artwork-edit-error {
    background: var(--color-error);
    color: #FBF8F3;
    padding: 0.6rem 0.85rem;
    border-radius: 4px;
    font-size: 0.85rem;
    margin: 0 0 1rem;
}

/* Click-to-edit rows */
.artwork-edit-fields {
    margin: 0 0 1.5rem;
}

.artwork-edit-field {
    display: grid;
    grid-template-columns: 9rem 1fr;
    gap: 0.75rem;
    align-items: baseline;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--color-nav-border);
}

.artwork-edit-field dt {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-text-muted);
}

.artwork-edit-field dd {
    margin: 0;
}

.artwork-edit-value {
    background: none;
    border: none;
    border-bottom: 1px dashed var(--color-nav-border);
    font: inherit;
    color: var(--color-text);
    text-align: left;
    padding: 0.15rem 0;
    cursor: text;
    width: 100%;
}

.artwork-edit-value:hover {
    border-bottom-color: var(--color-text-muted);
    background: rgba(255, 255, 255, 0.4);
}

.artwork-edit-value:focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 2px;
}

.artwork-edit-input {
    font: inherit;
    width: 100%;
    padding: 0.3rem 0.5rem;
    border: 1px solid var(--color-accent-deep);
    border-radius: 3px;
    background: var(--color-bg);
    color: var(--color-text);
}

/* Medium "add new", and any other place a field and its buttons share a row. */
.artwork-edit-inline {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

/* Height and width, labelled rather than positional -- the free-text field these
   replace held both orders at once and nothing said which. */
.artwork-edit-size {
    display: flex;
    align-items: flex-end;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.artwork-edit-sizefield {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.artwork-edit-sizefield span {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-text-muted);
}

.artwork-edit-sizefield input {
    width: 6rem;
}

.artwork-edit-sizetimes {
    padding-bottom: 0.45rem;
    color: var(--color-text-muted);
}

.artwork-edit-sizeunit {
    padding-bottom: 0.45rem;
    font-size: 0.8rem;
    color: var(--color-text-muted);
}

/* The status the two availability checkboxes add up to, including the one that is
   not a checkbox at all. */
.artwork-edit-derived {
    margin: 0.75rem 0 0;
    font-size: 0.82rem;
    color: var(--color-text-muted);
}

.artwork-edit-derived strong {
    color: var(--color-text);
    font-weight: 600;
}

.artwork-edit-empty {
    margin: 0 0 0.75rem;
    font-size: 0.85rem;
    color: var(--color-text-muted);
}

/* Ada's own labels, shown as removable chips. */
.artwork-edit-taglist {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    list-style: none;
    margin: 0 0 0.85rem;
    padding: 0;
}

.artwork-edit-tagchip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.2rem 0.3rem 0.2rem 0.6rem;
    border: 1px solid var(--color-nav-border);
    border-radius: 999px;
    background: var(--color-bg);
    font-size: 0.82rem;
}

.artwork-edit-tagremove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--color-text-muted);
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}

.artwork-edit-tagremove:hover {
    background: rgba(0, 0, 0, 0.08);
    color: var(--color-text);
}

.artwork-edit-tagremove:focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 1px;
}

.artwork-edit-tagadd {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.artwork-edit-tagadd .artwork-edit-input {
    flex: 1 1 auto;
    max-width: 18rem;
}

/* Accent colour: the swatch and the hex field sit on one row, and either
   drives the other. The swatch is for choosing a colour, the text field for
   pasting one you already know. */
.artwork-edit-colour {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.artwork-edit-swatch {
    flex: 0 0 auto;
    width: 2.4rem;
    height: 2rem;
    padding: 0;
    border: 1px solid var(--color-nav-border);
    border-radius: 3px;
    background: none;
    cursor: pointer;
}

.artwork-edit-swatch:focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 2px;
}

.artwork-edit-hex {
    flex: 1 1 auto;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    text-transform: uppercase;
}

/* The Year field. A dropdown rather than the click-to-edit text used by the
   fields around it, because its value is constrained: every year it will accept
   is on the list, so there is nothing to type and nothing to mistype. */
.artwork-edit-select {
    font: inherit;
    padding: 0.3rem 0.5rem;
    border: 1px solid var(--color-nav-border);
    border-radius: 3px;
    background: var(--color-bg);
    color: var(--color-text);
    cursor: pointer;
    min-width: 8rem;
}

.artwork-edit-select:hover {
    border-color: var(--color-text-muted);
}

.artwork-edit-select:focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 2px;
}

.artwork-edit-flags,
.artwork-edit-tags {
    border: 1px solid var(--color-nav-border);
    border-radius: 4px;
    padding: 0.75rem 1rem 1rem;
    margin: 0 0 1.25rem;
}

.artwork-edit-flags legend,
.artwork-edit-tags legend {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-text-muted);
    padding: 0 0.4rem;
}

.artwork-edit-flags label,
.artwork-edit-tags label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0.3rem 1rem 0.3rem 0;
    font-size: 0.88rem;
    cursor: pointer;
}

.artwork-edit-tags label.is-selected {
    color: var(--color-accent-deep);
    font-weight: 600;
}

/* Moderation row */
.artwork-edit-moderation {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding-top: 1rem;
    border-top: 1px solid var(--color-nav-border);
}

.artwork-edit-moderation-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.artwork-edit-badge {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0.2rem 0.5rem;
    border-radius: 3px;
}

.artwork-edit-badge.is-live {
    background: rgba(168, 181, 162, 0.3);
    color: #4A5A44;
}

.artwork-edit-badge.is-hidden {
    background: rgba(201, 168, 130, 0.3);
    color: #7A5A30;
}

.artwork-edit-badge.is-deleted {
    background: rgba(168, 90, 60, 0.2);
    color: var(--color-error);
}

.artwork-edit-action {
    display: inline-block;
    background: var(--color-bg);
    border: 1px solid var(--color-nav-border);
    border-radius: 4px;
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: 0.82rem;
    padding: 0.35rem 0.75rem;
    cursor: pointer;
    text-decoration: none;
    transition: border-color var(--transition-base), background var(--transition-base);
}

.artwork-edit-action:hover:not(:disabled) {
    border-color: var(--color-text-muted);
}

.artwork-edit-action:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.artwork-edit-action.is-danger {
    color: var(--color-error);
    border-color: rgba(168, 90, 60, 0.4);
}

.artwork-edit-action.is-danger:hover:not(:disabled) {
    background: rgba(168, 90, 60, 0.08);
    border-color: var(--color-error);
}

.artwork-edit-action:focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 2px;
}

.artwork-edit-confirm {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    font-size: 0.82rem;
    color: var(--color-text);
}

/* Deleted-pieces page */
.admin-page {
    max-width: 900px;
    margin: 2.5rem auto 6rem;
    padding: 0 1.25rem;
    font-family: var(--font-body);
}

.admin-page-title {
    font-family: var(--font-heading);
    font-size: 2.2rem;
    font-weight: 300;
    margin: 0 0 0.5rem;
    color: var(--color-text);
}

.admin-page-lede {
    color: var(--color-text-muted);
    font-size: 0.9rem;
    max-width: 46rem;
    margin: 0 0 1rem;
}

.deleted-list {
    list-style: none;
    margin: 2rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.deleted-item {
    display: grid;
    grid-template-columns: 5rem 1fr auto;
    gap: 1rem;
    align-items: center;
    padding: 0.85rem;
    border: 1px solid var(--color-nav-border);
    border-radius: var(--radius-card);
    background: var(--color-nav-bg);
}

.deleted-item-thumb {
    width: 5rem;
    height: 4rem;
    object-fit: cover;
    border-radius: 3px;
    /* Deleted pieces read as set aside rather than current. */
    filter: grayscale(0.6);
    opacity: 0.8;
}

.deleted-item-title {
    font-weight: 600;
    margin: 0 0 0.15rem;
    color: var(--color-text);
}

.deleted-item-meta,
.deleted-item-when {
    margin: 0;
    font-size: 0.8rem;
    color: var(--color-text-muted);
}

.deleted-item-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    justify-content: flex-end;
}

@media (max-width: 40rem) {
    .deleted-item {
        grid-template-columns: 4rem 1fr;
    }

    .deleted-item-actions {
        grid-column: 1 / -1;
        justify-content: flex-start;
    }

    .artwork-edit-field {
        grid-template-columns: 1fr;
        gap: 0.2rem;
    }
}

.version-history-paging {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    max-width: 26rem;
    margin-top: 0.5rem;
    padding: 0.4rem 0.85rem;
    border: 1px solid var(--color-nav-border);
    border-radius: var(--radius-card);
    background: var(--color-bg);
}

.version-history-page-status {
    color: var(--color-text-muted);
    font-size: 0.76rem;
    font-variant-numeric: tabular-nums;
}

.version-history-page-button {
    background: none;
    border: 1px solid var(--color-nav-border);
    border-radius: 4px;
    color: var(--color-text);
    font: inherit;
    font-size: 0.76rem;
    padding: 0.2rem 0.6rem;
    cursor: pointer;
    transition: border-color var(--transition-base), background var(--transition-base);
}

.version-history-page-button:hover:not(:disabled) {
    background: var(--color-nav-bg);
    border-color: var(--color-text-muted);
}

.version-history-page-button:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.version-history-page-button:focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 2px;
}

/* =========================================================
   Home page curation (Phase 6)
   Rendered only inside HomeCurationWorkspace, which itself renders only for an
   allowlisted admin — none of these class names reach an anonymous visitor.
   ========================================================= */

.curation-panel {
    max-width: var(--content-max, 1200px);
    /* A strip of page between the hero's band and the panel, as much as below it:
       butted against the band, the panel looked like part of it. */
    margin: 1.5rem auto;
    padding: 1.25rem 1.5rem 1.4rem;
    background: var(--color-nav-bg);
    border: 1px solid rgba(0, 0, 0, 0.07);
    border-radius: var(--radius-card);
    font-family: var(--font-body);
}

.curation-panel-head {
    margin-bottom: 1.1rem;
}

.curation-heading {
    font-family: var(--font-heading);
    font-size: 1.35rem;
    font-weight: 600;
    margin: 0 0 0.3rem;
    color: var(--color-text);
}

.curation-hint {
    margin: 0;
    font-size: 0.85rem;
    opacity: 0.75;
}

.curation-hint-grip {
    font-size: 0.95rem;
    letter-spacing: -0.05em;
}

.curation-subheading {
    font-family: var(--font-body);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    margin: 0 0 0.65rem;
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
}

.curation-count {
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    opacity: 0.6;
}

.curation-empty,
.curation-blocked {
    margin: 0;
    font-size: 0.85rem;
    opacity: 0.75;
}

.curation-blocked {
    max-width: var(--content-max, 1200px);
    margin: 0 auto 1rem;
    padding: 0.55rem 0.9rem;
    background: var(--color-nav-bg);
    border-radius: var(--radius-card);
    font-family: var(--font-body);
}

.curation-error {
    margin: 0.9rem 0 0;
    font-size: 0.85rem;
    color: #8C2F1E;
}

/* ---- the carousel strip ---- */

.curation-strip {
    list-style: none;
    margin: 0;
    padding: 0.25rem 0 0.5rem;
    display: flex;
    gap: 0.75rem;
    overflow-x: auto;
}

.curation-strip-item {
    position: relative;
    flex: 0 0 auto;
    width: 8.5rem;
    padding: 0.5rem;
    background: var(--color-bg);
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-radius: var(--radius-card);
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.curation-strip-thumb {
    width: 100%;
    height: 4.5rem;
    object-fit: cover;
    border-radius: calc(var(--radius-card) / 2);
    display: block;
    background: #EDE8E0;
}

.curation-strip-title {
    font-size: 0.75rem;
    line-height: 1.25;
    /* The title wraps rather than truncating: a row of pieces called "Portrait of…"
       tells Ada nothing about which one she is about to move. */
    overflow-wrap: anywhere;
}

.curation-strip-actions {
    display: flex;
    gap: 0.25rem;
    margin-top: auto;
    align-items: center;
}

/* ---- the controls on a gallery tile ---- */

.curation-item {
    position: relative;
}

.curation-tile {
    position: absolute;
    top: 0.5rem;
    left: 0.5rem;
    right: 0.5rem;
    z-index: 4;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem;
    padding: 0.35rem;
    background: rgba(251, 248, 243, 0.94);
    border-radius: calc(var(--radius-card) / 2);
    box-shadow: var(--shadow-card);
    font-family: var(--font-body);
}

.curation-position {
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    opacity: 0.65;
    min-width: 1.1rem;
    text-align: center;
}

/* ---- shared buttons ---- */

.curation-grip,
.curation-move,
.curation-remove,
.curation-feature {
    font: inherit;
    font-size: 0.72rem;
    line-height: 1;
    padding: 0.32rem 0.45rem;
    border: 1px solid rgba(0, 0, 0, 0.15);
    border-radius: calc(var(--radius-card) / 2);
    background: var(--color-bg);
    color: var(--color-text);
    cursor: pointer;
    transition: background var(--transition-base), border-color var(--transition-base);
}

.curation-grip:hover,
.curation-move:hover:not(:disabled),
.curation-remove:hover:not(:disabled),
.curation-feature:hover:not(:disabled) {
    background: var(--color-nav-bg);
    border-color: rgba(0, 0, 0, 0.3);
}

.curation-move:disabled,
.curation-remove:disabled,
.curation-feature:disabled {
    opacity: 0.4;
    cursor: default;
}

.curation-feature.is-on {
    background: var(--color-accent-deep);
    border-color: var(--color-accent-deep);
    color: #FBF8F3;
}

.curation-grip {
    display: inline-block;
    /* The strip item is a flex column, which would otherwise stretch the grip across
       the whole card and make a small handle look like a wide empty button. */
    align-self: flex-start;
    user-select: none;
    cursor: grab;
    /* Without this the browser claims the gesture as a scroll before the first
       pointermove arrives, and a drag on a touch screen is impossible. */
    touch-action: none;
    letter-spacing: -0.05em;
}

.curation-grip:active {
    cursor: grabbing;
}

/* ---- drag feedback ---- */

.curation-item.is-dragging,
.curation-strip-item.is-dragging {
    opacity: 0.45;
}

.is-reordering .is-drop-before,
.is-reordering .is-drop-after {
    position: relative;
}

/* The line marks the gap the piece will land in — before or after whatever is under
   the pointer — rather than highlighting a target, which says where the finger is
   but not what will happen when it lifts. */
.is-reordering .is-drop-before::after,
.is-reordering .is-drop-after::after {
    content: "";
    position: absolute;
    background: var(--color-accent-deep);
    border-radius: 2px;
    z-index: 5;
}

.gallery-grid.is-reordering .is-drop-before::after,
.gallery-grid.is-reordering .is-drop-after::after {
    left: -0.6rem;
    right: -0.6rem;
    height: 3px;
}

.gallery-grid.is-reordering .is-drop-before::after { top: -0.35rem; }
.gallery-grid.is-reordering .is-drop-after::after { bottom: -0.35rem; }

.curation-strip.is-reordering .is-drop-before::after,
.curation-strip.is-reordering .is-drop-after::after {
    top: -0.2rem;
    bottom: -0.2rem;
    width: 3px;
}

.curation-strip.is-reordering .is-drop-before::after { left: -0.4rem; }
.curation-strip.is-reordering .is-drop-after::after { right: -0.4rem; }

@media (prefers-reduced-motion: reduce) {
    .curation-grip,
    .curation-move,
    .curation-remove,
    .curation-feature {
        transition: none;
    }
}

/* ---------------------------------------------------------------
   Replace-photo control (Phase 4)
   Inside the artwork editor, which renders only for an admin.
   --------------------------------------------------------------- */

.artwork-edit-photo {
    margin: 1.25rem 0;
    padding: 0.9rem 1rem;
    background: var(--color-nav-bg);
    border-radius: var(--radius-card);
    font-family: var(--font-body);
}

.artwork-edit-photo-label {
    display: block;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    margin-bottom: 0.6rem;
}

/* The real input is hidden and the label is the control, because a browser's own
   file button cannot be styled and reads as "No file chosen" — which is a status,
   not an instruction, and the wrong thing to put in front of someone. */
.artwork-edit-photo-choose {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid rgba(0, 0, 0, 0.2);
    border-radius: calc(var(--radius-card) / 2);
    background: var(--color-bg);
    cursor: pointer;
    font-size: 0.85rem;
    transition: background var(--transition-base), border-color var(--transition-base);
}

.artwork-edit-photo-choose:hover {
    background: var(--color-nav-bg);
    border-color: rgba(0, 0, 0, 0.35);
}

.artwork-edit-photo-choose.is-busy {
    opacity: 0.6;
    cursor: progress;
}

.artwork-edit-photo-choose input[type="file"] {
    /* Not display:none — that removes it from the tab order, so the control would be
       unreachable from a keyboard. Clipped instead, so it is still focusable and the
       label still activates it. */
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.artwork-edit-photo-choose:focus-within {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 2px;
}

.artwork-edit-photo-note {
    margin: 0.6rem 0 0;
    font-size: 0.8rem;
    opacity: 0.75;
    line-height: 1.45;
}

.artwork-edit-photo-error {
    margin: 0.6rem 0 0;
    font-size: 0.82rem;
    line-height: 1.45;
    color: #8C2F1E;
}

@media (prefers-reduced-motion: reduce) {
    .artwork-edit-photo-choose {
        transition: none;
    }
}

/* Upload states. The control had one silent state before this: the upload ran, the
   draft saved, and nothing on screen changed. */

.artwork-edit-photo-ready {
    margin: 0.6rem 0 0;
    padding: 0.5rem 0.75rem;
    font-size: 0.82rem;
    line-height: 1.45;
    background: rgba(122, 138, 92, 0.14);
    border-left: 3px solid var(--color-accent-deep);
    border-radius: calc(var(--radius-card) / 3);
}

.artwork-edit-photo-spinner {
    width: 0.85rem;
    height: 0.85rem;
    border: 2px solid rgba(0, 0, 0, 0.18);
    border-top-color: var(--color-accent-deep);
    border-radius: 50%;
    animation: artwork-edit-spin 0.7s linear infinite;
    flex-shrink: 0;
}

@keyframes artwork-edit-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .artwork-edit-photo-spinner {
        /* Still visible as a state, just not turning. */
        animation: none;
        border-top-color: var(--color-accent-deep);
    }
}

/* ------------------------------------------------------------------
   Phase 5: adding a piece, and the pieces that are not on the site
   Admin-only surfaces. None of this markup is rendered for a visitor —
   see GalleryWithFilter's HiddenSlugs and the AdminOnly branches.
   ------------------------------------------------------------------ */

.curation-add {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    margin-top: 0.85rem;
    background: var(--color-accent-deep);
    border: 1px solid var(--color-accent-deep);
    border-radius: 4px;
    color: #FBF8F3;
    font: inherit;
    font-weight: 600;
    font-size: 0.85rem;
    padding: 0.45rem 0.9rem;
    cursor: pointer;
    transition: background var(--transition-base);
}

.curation-add:hover:not(:disabled) {
    background: var(--color-accent-deep-hover);
}

.curation-add:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.curation-add-mark {
    font-size: 1.05rem;
    line-height: 1;
}

/* ----- The Add artwork dialog ----- */

.add-artwork {
    border: 1px solid var(--color-nav-border);
    border-radius: var(--radius-card);
    background: var(--color-bg);
    color: var(--color-text);
    padding: 0;
    width: min(28rem, calc(100vw - 2rem));
    box-shadow: var(--shadow-hover);
}

.add-artwork::backdrop {
    background: var(--color-overlay-bg);
}

.add-artwork-form {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 1.5rem;
    font-family: var(--font-body);
}

.add-artwork-heading {
    font-family: var(--font-display);
    font-size: 1.35rem;
    margin: 0 0 0.25rem;
}

.add-artwork-label {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-text-muted);
}

.add-artwork-input {
    font: inherit;
    font-size: 1rem;
    padding: 0.5rem 0.65rem;
    border: 1px solid var(--color-nav-border);
    border-radius: 4px;
    background: #FFFFFF;
    color: var(--color-text);
}

.add-artwork-input:focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 1px;
}

/* Its own line and a fixed minimum height, so the panel does not jump the moment
   the first letter is typed and the address appears. */
.add-artwork-url {
    margin: 0;
    min-height: 1.4rem;
    font-size: 0.85rem;
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    flex-wrap: wrap;
}

.add-artwork-url-empty {
    color: var(--color-text-muted);
    font-style: italic;
}

.add-artwork-url-label {
    color: var(--color-text-muted);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.add-artwork-url-value {
    font-family: var(--font-body);
    font-weight: 600;
    word-break: break-all;
}

.add-artwork-note {
    margin: 0;
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--color-text-muted);
}

.add-artwork-error {
    margin: 0;
    font-size: 0.85rem;
    color: var(--color-error);
}

.add-artwork-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.6rem;
    margin-top: 0.4rem;
}

.add-artwork-cancel,
.add-artwork-create {
    font: inherit;
    font-weight: 600;
    font-size: 0.85rem;
    border-radius: 4px;
    padding: 0.45rem 0.9rem;
    cursor: pointer;
    white-space: nowrap;
}

.add-artwork-cancel {
    background: none;
    border: 1px solid var(--color-nav-border);
    color: var(--color-text);
}

.add-artwork-cancel:hover:not(:disabled) {
    border-color: var(--color-text-muted);
}

.add-artwork-create {
    background: var(--color-accent-deep);
    border: 1px solid var(--color-accent-deep);
    color: #FBF8F3;
}

.add-artwork-create:hover:not(:disabled) {
    background: var(--color-accent-deep-hover);
}

.add-artwork-cancel:disabled,
.add-artwork-create:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

/* ----- Pieces that are not on the site ----- */

.gallery-hidden {
    margin-top: 3.5rem;
    padding-top: 2rem;
    border-top: 1px solid var(--color-nav-border);
}

.gallery-hidden-heading {
    font-family: var(--font-display);
    font-size: 1.3rem;
    margin: 0;
    display: flex;
    align-items: baseline;
    gap: 0.55rem;
    color: var(--color-text-muted);
}

.gallery-hidden-count {
    font-family: var(--font-body);
    font-size: 0.75rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    background: var(--color-nav-bg);
    border-radius: 999px;
    padding: 0.1rem 0.5rem;
}

.gallery-hidden-note {
    margin: 0.35rem 0 1.5rem;
    font-family: var(--font-body);
    font-size: 0.85rem;
    color: var(--color-text-muted);
}

/* Faded, but not so far that Ada cannot tell which painting it is -- the point of
   showing them at all is that choosing what to hide is easier looking at the work.
   Restored to full on hover, because at that moment she is looking at that one. */
.artwork-card.is-hidden-piece .artwork-card-link {
    opacity: 0.55;
    filter: saturate(0.55);
    transition: opacity var(--transition-base), filter var(--transition-base);
}

.artwork-card.is-hidden-piece:hover .artwork-card-link,
.artwork-card.is-hidden-piece:focus-within .artwork-card-link {
    opacity: 1;
    filter: none;
}

/* The mark says why it is faded. Over the image rather than beside it, so it reads at a
   glance in a grid -- and at the bottom, because the top of every tile is taken by the
   controls, which are opaque and would sit on top of it. */
.artwork-hidden-mark {
    position: absolute;
    bottom: 0.6rem;
    left: 0.6rem;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 50%;
    background: var(--color-overlay-bg);
    color: #FBF8F3;
    pointer-events: none;
}

/* ----- A piece added but not yet photographed ----- */

.artwork-image-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    aspect-ratio: 4 / 5;
    background: repeating-linear-gradient(
        135deg,
        var(--color-nav-bg),
        var(--color-nav-bg) 10px,
        #EFE9DF 10px,
        #EFE9DF 20px
    );
    color: var(--color-text-muted);
    font-family: var(--font-body);
}

.artwork-image-placeholder-mark {
    font-size: 2rem;
    line-height: 1;
    opacity: 0.5;
}

.artwork-image-placeholder-text {
    font-size: 0.8rem;
}

.curation-strip-thumb.is-empty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--color-nav-bg);
    color: var(--color-text-muted);
    font-size: 1.1rem;
}

/* ----- The immediate control, held apart from the drafted ones ----- */

/* The tile is near-white, not the publish bar's brown -- so these take their colours
   from .curation-feature beside them rather than from the bar's palette. Written the
   other way round first, which put white text on a white tile. */
.curation-tile-divider {
    width: 1px;
    align-self: stretch;
    margin: 0.1rem 0.15rem;
    background: rgba(0, 0, 0, 0.15);
}

/* Dashed, and the only dashed thing on the tile: the controls beside it are an
   arrangement waiting for Publish, and this one takes effect on the click. */
.curation-visibility {
    font: inherit;
    font-size: 0.72rem;
    line-height: 1;
    font-weight: 600;
    padding: 0.32rem 0.5rem;
    border: 1px dashed rgba(0, 0, 0, 0.3);
    border-radius: calc(var(--radius-card) / 2);
    background: var(--color-bg);
    color: var(--color-error);
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--transition-base), border-color var(--transition-base);
}

.curation-visibility:hover:not(:disabled) {
    background: var(--color-nav-bg);
    border-color: var(--color-error);
}

.curation-visibility:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

/* Solid and filled, because it is the only thing on a hidden tile and the one act
   that matters there -- where "Hide now" is one option among several. */
.curation-visibility.is-show {
    border-style: solid;
    background: var(--color-accent-deep);
    border-color: var(--color-accent-deep);
    color: #FBF8F3;
    font-size: 0.78rem;
    padding: 0.34rem 0.75rem;
}

.curation-visibility.is-show:hover:not(:disabled) {
    background: var(--color-accent-deep-hover);
    border-color: var(--color-accent-deep-hover);
}

.curation-tile.is-restore {
    justify-content: center;
}

/* ----- The banner on a hidden piece's own page ----- */

.artwork-hidden-banner {
    max-width: 860px;
    margin: 1.5rem auto 0;
    padding: 0.85rem 1.25rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    background: var(--color-nav-bg);
    border: 1px solid var(--color-nav-border);
    border-left: 4px solid var(--color-accent-deep);
    border-radius: var(--radius-card);
    font-family: var(--font-body);
    font-size: 0.88rem;
    line-height: 1.5;
    color: var(--color-text);
}

.artwork-hidden-banner-mark {
    flex-shrink: 0;
    display: inline-flex;
    color: var(--color-accent-deep);
}

.artwork-hidden-banner-text em {
    font-style: normal;
    font-weight: 600;
}

/* On the detail page the frame is 4:3 rather than the grid's 4:5, and the wrapper
   already reserves the space, so the placeholder fills it instead of setting its own. */
.detail-image-wrap .artwork-image-placeholder {
    aspect-ratio: auto;
    width: 100%;
    height: 100%;
}

/* ------------------------------------------------------------------
   Phase 7 and 8: editing the prose pages
   Admin-only surfaces, rendered inside <AdminOnly> or behind an
   [Authorize] route, so none of this reaches a visitor's page.
   ------------------------------------------------------------------ */

.bio-heading {
    font-family: var(--font-display);
    font-size: 1.5rem;
    font-weight: 600;
    margin: 2.5rem 0 0.75rem;
    color: var(--color-text);
}

.blocks-edit {
    max-width: 860px;
    margin: 3rem auto 6rem;
    padding: 0 1.25rem;
    font-family: var(--font-body);
}

.blocks-edit-head {
    border-top: 1px solid var(--color-nav-border);
    padding-top: 1.5rem;
}

.blocks-edit-heading {
    font-family: var(--font-display);
    font-size: 1.5rem;
    margin: 0;
}

.blocks-edit-hint,
.blocks-hint {
    margin: 0.3rem 0 0;
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--color-text-muted);
}

.blocks-edit-error {
    margin: 0.75rem 0 0;
    font-size: 0.9rem;
    color: var(--color-error);
}

.blocks-list {
    list-style: none;
    margin: 1.5rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.blocks-item {
    border: 1px solid var(--color-nav-border);
    border-radius: var(--radius-card);
    background: var(--color-bg);
    padding: 0.85rem 1rem 1rem;
}

/* The header's blocks are a fixed shape with nowhere to move to, so they read as
   settings rather than as things in a list. */
.blocks-item.is-hero {
    background: var(--color-nav-bg);
}

.blocks-item-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}

.blocks-item-type {
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-text-muted);
}

.blocks-item-actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex-wrap: wrap;
}

.blocks-confirm {
    font-size: 0.8rem;
    color: var(--color-text);
}

.blocks-move,
.blocks-remove,
.blocks-add-button {
    font: inherit;
    font-size: 0.75rem;
    line-height: 1;
    padding: 0.35rem 0.55rem;
    border: 1px solid var(--color-nav-border);
    border-radius: 4px;
    background: var(--color-bg);
    color: var(--color-text);
    cursor: pointer;
    white-space: nowrap;
    transition: border-color var(--transition-base), background var(--transition-base);
}

.blocks-move:hover:not(:disabled),
.blocks-remove:hover:not(:disabled),
.blocks-add-button:hover:not(:disabled) {
    border-color: var(--color-text-muted);
    background: var(--color-nav-bg);
}

.blocks-move:disabled,
.blocks-remove:disabled,
.blocks-add-button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.blocks-remove.is-danger {
    color: var(--color-error);
    border-color: rgba(168, 90, 60, 0.4);
}

.blocks-input,
.blocks-textarea {
    display: block;
    width: 100%;
    font: inherit;
    font-size: 0.95rem;
    line-height: 1.6;
    padding: 0.5rem 0.65rem;
    border: 1px solid var(--color-nav-border);
    border-radius: 4px;
    background: #FFFFFF;
    color: var(--color-text);
}

.blocks-textarea {
    resize: vertical;
    min-height: 3rem;
}

.blocks-textarea.is-short {
    min-height: 2.5rem;
}

.blocks-input:focus-visible,
.blocks-textarea:focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 1px;
}

.blocks-label {
    display: block;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--color-text-muted);
    margin-bottom: 0.25rem;
}

/* ----- A figure's image and its alt text ----- */

.blocks-image {
    display: flex;
    gap: 1rem;
    align-items: flex-start;
    flex-wrap: wrap;
}

.blocks-image-preview {
    flex: 0 0 9rem;
    aspect-ratio: 4 / 3;
    border-radius: 4px;
    overflow: hidden;
    background: var(--color-nav-bg);
    display: flex;
    align-items: center;
    justify-content: center;
}

.blocks-image-preview img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.blocks-image-empty {
    font-size: 0.75rem;
    color: var(--color-text-muted);
    text-align: center;
    padding: 0 0.5rem;
}

.blocks-image-fields {
    flex: 1 1 16rem;
    min-width: 0;
}

.blocks-upload {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.75rem;
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
}

/* The native control says "No file chosen" next to a button nobody styled. The label
   around it is the affordance; the input itself only needs to stay reachable. */
.blocks-upload input[type="file"] {
    font-size: 0.75rem;
    max-width: 13rem;
}

.blocks-uploading {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.75rem 0 0;
    font-size: 0.8rem;
    color: var(--color-text-muted);
}

.blocks-spinner {
    width: 0.9rem;
    height: 0.9rem;
    border: 2px solid var(--color-nav-border);
    border-top-color: var(--color-accent-deep);
    border-radius: 50%;
    animation: artwork-edit-spin 0.7s linear infinite;
    flex-shrink: 0;
}

.blocks-photo-error {
    margin: 0.5rem 0 0;
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--color-error);
}

/* ----- Adding a block ----- */

.blocks-add {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 1.25rem;
}

.blocks-add-label {
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-text-muted);
    margin-right: 0.25rem;
}

/* ----- The contact page's settings ----- */

.contact-settings {
    max-width: 860px;
    margin: 3rem auto 0;
    padding: 1.5rem 1.25rem 0;
    border-top: 1px solid var(--color-nav-border);
    font-family: var(--font-body);
}

.contact-settings-heading {
    font-family: var(--font-display);
    font-size: 1.5rem;
    margin: 0;
}

.contact-settings-hint {
    margin: 0.3rem 0 1.25rem;
    font-size: 0.8rem;
    color: var(--color-text-muted);
}

.contact-settings-field {
    margin-bottom: 1.1rem;
}

.contact-settings-error {
    margin: 0 0 0.75rem;
    font-size: 0.85rem;
    color: var(--color-error);
}

.contact-settings-saved {
    margin: 0 0 0.75rem;
    font-size: 0.85rem;
    color: var(--color-accent-deep);
}

.contact-settings-save {
    font: inherit;
    font-weight: 600;
    font-size: 0.85rem;
    padding: 0.45rem 0.9rem;
    border-radius: 4px;
    background: var(--color-accent-deep);
    border: 1px solid var(--color-accent-deep);
    color: #FBF8F3;
    cursor: pointer;
    transition: background var(--transition-base);
}

.contact-settings-save:hover:not(:disabled) {
    background: var(--color-accent-deep-hover);
}

.contact-settings-save:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}
