/* Work Layout */
.work-layout {
    display: flex;
    width: 100%;
    height: 100%;
    position: relative;
    container-type: inline-size;
}

.work-layout>.content-viewport {
    flex: 1;
    min-width: 0;
    padding: 2rem;
    /* Clear the "Work" label that sits in the bottom-right corner: reserve the
       label's line box (~1.2× the font size) + its 2rem padding + a 2rem gap,
       so the last project can scroll up to ~2rem clearance above the label's
       top edge even on large screens where the label is tallest. */
    padding-bottom: calc(var(--category-label-size) * 1.2 + 4rem);
    overflow-y: auto;
    /* Scrollbar returns to viewport edge */
    height: 100%;
    position: relative;
    scroll-behavior: smooth;
}

/* Filter Bar Pill Redesign */
.filter-spacer {
    width: 44px;
    height: 44px;
    margin-bottom: 2rem;
    position: relative;
}

.filter-wrapper {
    position: absolute;
    right: 0;
    top: 0;
    display: flex;
    flex-direction: row-reverse;
    align-items: center;
    z-index: 50;
    background: var(--bg-color);
    border: 1px solid var(--text-color);
    border-radius: 999px;
    height: 44px;
    width: max-content;
    overflow: hidden;
}

#work-filter-toggle {
    position: relative;
    background: transparent;
    border: none;
    color: var(--text-color);
    width: 42px; /* Leaves space for the 1px wrapper border */
    height: 42px;
    flex-shrink: 0;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 22;
    transition: opacity 0.2s ease;
    border-radius: 50%;
}

#work-filter-toggle:focus-visible {
    outline-offset: -2px;
}

/* Open state: cross-fade the magnifier (search) into an X (close). */
#work-filter-toggle .ms-icon {
    position: absolute;
    font-size: 22px;
    transition: opacity 0.2s ease;
}

#work-filter-toggle .tg-close { opacity: 0; }
#work-filter-toggle[aria-expanded="true"] .tg-open { opacity: 0; }
#work-filter-toggle[aria-expanded="true"] .tg-close { opacity: 1; }

.work-filter {
    /* Expanded width that lands the bar's left edge on the project text column:
       full width − content left pad − sidebar right offset (2× inset)
       − image column − the 2rem gutter − the ~44px toggle.
       No lower floor: the bar may only shrink toward the toggle, never grow past
       the text column into the image column/gutter. If the room ever computes
       negative the max() floors it at 0 (bar stays closed) rather than letting an
       invalid value drop the cap and blow the bar full-width. The compact layout
       (≤1024px) overrides this with its own full-width rule. */
    --work-filter-w: max(0px, min(calc(100cqw - 2 * var(--content-inset) - var(--work-image-col) - 2rem - 44px), var(--search-cap)));
    display: flex;
    align-items: center;
    height: 100%;
    max-width: var(--work-filter-w);
    padding-left: 1.5rem;
    padding-right: 0.5rem;
    overflow: hidden;
    transition: max-width 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), padding 0.4s ease, visibility 0.4s ease;
    visibility: visible;
}

.work-filter.collapsed {
    max-width: 0;
    padding-left: 0;
    padding-right: 0;
    pointer-events: none;
    visibility: hidden;
}

.filter-controls {
    display: flex;
    align-items: center;
    gap: 1rem;
    height: 100%;
    width: calc(var(--work-filter-w) - 2rem);
    opacity: 1;
    transition: opacity 0.2s ease;
}

.work-filter.collapsed .filter-controls {
    opacity: 0;
    transition: opacity 0.2s ease;
}

.filter-divider {
    width: 1px;
    height: 20px;
    background-color: var(--text-color);
}

.work-filter input,
.work-filter select {
    font-family: var(--font-body);
    font-size: calc((1.1rem) * var(--font-scale));
    border: none;
    background: transparent;
    color: var(--text-color);
    outline: none;
    padding: 0;
    margin: 0;
    transition: var(--page-color-transition);
}

.work-filter input:focus-visible,
.work-filter select:focus-visible {
    outline: 2px dashed var(--text-color);
    outline-offset: -2px;
    border-radius: 2px;
}

.work-filter input {
    width: 100%;
    flex: 1;
    min-width: 0;
}

.work-filter input::placeholder {
    color: var(--text-color);
}

.work-filter select {
    cursor: pointer;
    appearance: none;
    padding-right: 1.5rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right center;
}


/* Project Cards */
.project-card {
    display: flex;
    flex-wrap: wrap;
    gap: 2rem;
    margin-bottom: 4rem;
}

/* The viewport's padding-bottom owns the clearance below the final project. */
.project-card:last-child {
    margin-bottom: 0;
}

/* The gallery is a column: the image viewport (which holds the old single-image
   column measure) with the pearlstring sitting beneath it. */
.project-gallery {
    /* Small inter-slide gap so a centred image's neighbour sits just off-screen
       — without it, gap:0 lets the next image's 1px border peek at the edge. */
    --pg-gap: 0.5rem;
    /* Width budget an image may fill. The desktop column has no side inset; on
       phones the gallery is full-bleed but each image keeps a 2rem gap to the
       screen edges (see the container query below). */
    --pg-avail-w: 100cqw;
    flex: 1 1 var(--work-image-col);
    min-width: 0;
    max-width: min(100%, var(--work-image-col));
    align-self: flex-start;
    display: flex;
    flex-direction: column;
    position: relative; /* anchor the out-of-flow pearlstring below the image */
}

/* On phones the gallery fills the content column (so it keeps the viewport's
   2rem side margins) and puts 2rem of space between images as they scroll, like
   the Play lightbox's mobile view. */
@container (max-width: 500px) {
    .project-gallery {
        flex: none;
        max-width: none;
        /* Full-bleed: cancel the content-viewport's 2rem side padding so the
           scroll container reaches the screen edges and images slide in from
           there. Each image still keeps a 2rem gap to the edge (--pg-avail-w),
           which also keeps the neighbour off-screen, so no inter-slide gap is
           needed. */
        width: calc(100% + 4rem);
        margin-left: -2rem;
        margin-right: -2rem;
        --pg-gap: 0px;
        --pg-avail-w: calc(100cqw - 4rem);
    }
}

/* Desktop (image beside the text): when a project's text runs longer than the
   image, keep the gallery pinned 2rem from the top of the scroller as the prose
   scrolls past, rather than letting it ride up out of view. align-self:flex-start
   already keeps it from stretching, so sticky has a definite box to pin. */
@media (min-width: 1025px) {
    .project-gallery {
        position: sticky;
        top: 2rem;
    }
}

/* The framed box. Its aspect-ratio is set from the first image (JS); a 4/3
   fallback holds height before the image loads. overflow stays visible so the
   chevrons can straddle its side edges; the inner .pg-clip clips the track. */
.pg-viewport {
    position: relative;
    width: 100%;
    aspect-ratio: 4 / 3;
    overflow: visible;
    /* Allow both axes: a horizontal swipe scrolls the gallery, a vertical swipe
       falls through to the page — even with a single image. (pan-x alone blocked
       vertical page scroll when a swipe started on the gallery.) */
    touch-action: pan-x pan-y;
}

/* Clips the sliding track to the box. It must be a *stationary* ancestor of the
   track (clipping the track itself would move its clip region with it), so the
   chevrons live on .pg-viewport, outside this clip, and can overhang the edge. */
.pg-clip {
    position: absolute;
    inset: 0;
    overflow: hidden;
    /* Size query container so each slide can fit itself to the box by its own
       aspect (cqw/cqh), like the lightbox. */
    container-type: size;
}

/* Native horizontal scroll-snap carries the slides (smooth, like the Play
   lightbox) instead of a transformed track. position:relative so the slides'
   offsetParent is the track (the JS centres by offsetLeft). Scrollbar hidden. */
.project-gallery-track {
    position: relative;
    display: flex;
    height: 100%;
    /* Gap between slides (0 on desktop, 2rem on phones) so images are separated
       as they scroll, like the Play lightbox. */
    gap: var(--pg-gap, 0px);
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    /* Both axes: horizontal swipes scroll this track, vertical swipes scroll the
       page (the carousel must not eat vertical pans). */
    touch-action: pan-x pan-y;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.project-gallery-track::-webkit-scrollbar { display: none; }

/* Each slide is the full box (one image per view); the image inside is fitted to
   the box by its own aspect (set from its natural size in JS) and scaled up to
   match — so the 1px border hugs the image (no empty frames), a low-res source
   fills the frame while the full image loads, and narrow images stay centred
   without neighbours peeking in. The 1.333 fallback holds before load. */
.pg-slide {
    flex: 0 0 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    scroll-snap-align: center;
}

.project-gallery-img {
    width: min(var(--pg-avail-w, 100cqw), calc(100cqh * var(--pg-aspect, 1.333)));
    height: min(100cqh, calc(var(--pg-avail-w, 100cqw) / var(--pg-aspect, 1.333)));
    object-fit: contain; /* dimensions already match the aspect → fills exactly */
    display: block;
    border: 1px solid var(--text-color);
    user-select: none;
    cursor: zoom-in;
}

/* Cycle chevrons — Play-lightbox styling, desktop hover only. Solid (bordered)
   circles, like the fullscreen lightbox's, so they read as buttons rather than a
   transparent glyph floating on the image. */
.gallery-prev,
.gallery-next {
    position: absolute;
    top: 50%;
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--text-color);
    border-radius: 50%;
    background: var(--bg-color);
    color: var(--text-color);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.2s ease, background-color 0.2s ease;
    z-index: 2;
}

/* Centred on the image's side edges: half the circle straddles outside the box
   (which is why .pg-viewport is overflow:visible). */
.gallery-prev { left: 0; transform: translate(-50%, -50%); }
.gallery-next { right: 0; transform: translate(50%, -50%); }
.gallery-prev:hover,
.gallery-next:hover { background: var(--bg-color); }

/* Expand the chevron show-zone by the chevron's radius (19px) in every
   direction. This invisible region is a descendant of .project-gallery, so
   hovering it counts as hovering the gallery and reveals the chevrons — which
   keeps them reachable around the first/last item, where one chevron is hidden
   and the cursor would otherwise fall outside the image box. It sits behind the
   image (z-index:-1) so it never intercepts the image's own clicks. Hover-only:
   the chevrons don't exist on touch, and a full-bleed gallery would otherwise
   let this overhang the viewport and force a horizontal scrollbar. */
@media (hover: hover) and (pointer: fine) {
    .pg-viewport::after {
        content: '';
        position: absolute;
        inset: -19px;
        z-index: -1;
    }
}

/* Chevron icons: smaller on the inline gallery, larger in the lightbox. */
.gallery-prev .ms-icon,
.gallery-next .ms-icon {
    font-size: 24px;
}

.wlb-prev .ms-icon,
.wlb-next .ms-icon {
    font-size: 32px;
}

@media (hover: hover) and (pointer: fine) {
    /* Show on gallery hover, and keep showing while the cursor is over a chevron
       itself — its outside-the-box half extends the show-zone by half a circle,
       so moving onto that overhang doesn't drop the hover and flicker them off. */
    .project-gallery:hover .gallery-prev,
    .project-gallery:hover .gallery-next,
    .gallery-prev:hover,
    .gallery-next:hover { opacity: 1; }
}

/* Touch / no-hover devices: no chevrons — swipe + dots instead. */
@media (hover: none), (pointer: coarse) {
    .gallery-prev,
    .gallery-next { display: none !important; }
}

/* Pearlstring position indicator — sits just beneath the image. It's taken out
   of flow (absolute) so it lives within the 4rem gap to the next project rather
   than adding its own height to the card. On phones (stacked layout) it goes
   back in flow, between the gallery and the info below it. */
.gallery-dots {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    display: flex;
    justify-content: center;
    gap: 6px;
    margin-top: 0.6rem;
}

@container (max-width: 500px) {
    .gallery-dots {
        position: static;
    }
}

.gallery-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    border: 1px solid var(--text-color);
    background: transparent;
    cursor: pointer;
    transition: background-color 0.2s ease;
}

.gallery-dot.active {
    background: var(--text-color);
}

/* --- Work lightbox: bounded to the Work category (like the Play lightbox) --- */
.work-lightbox {
    position: absolute;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity 0.3s ease; /* the whole lightbox fades as one unit */
}

.work-lightbox[hidden] { display: none; }
.work-lightbox.active { opacity: 1; }

/* The lightbox covers the Work category, so hide the "Work" corner label (the
   controls would overlap it) and hide the base gallery so the open image isn't
   visible behind the blurred backdrop. */
body.work-lightbox-open #cell-work .category-trigger .label {
    opacity: 0;
}

body.work-lightbox-open #work-content .work-layout {
    visibility: hidden;
}

.work-lightbox-backdrop {
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--bg-color) 80%, transparent);
    backdrop-filter: blur(6px);
}

.work-lightbox-stage {
    position: relative;
    width: min(92%, 1200px);
    height: 82%;
    overflow: hidden; /* clips the off-screen slides of the track */
    touch-action: pan-x; /* horizontal swipes scroll the gallery */
    transform: scale(0.96);
    transition: transform 0.3s cubic-bezier(0.2, 0, 0.2, 1);
    /* Size query container so each slide can fit itself to the stage (cqw/cqh)
       by its own aspect, mirroring the Play lightbox. */
    container-type: size;
}

.work-lightbox.active .work-lightbox-stage {
    transform: scale(1);
}

/* Native horizontal scroll-snap carries the images (smooth, like the Play
   lightbox): each image is sized to its own aspect, separated by a 2rem gap,
   and centred via scroll-snap so it slides in from the stage sides with a
   margin (rather than full-width slides butted edge-to-edge). */
.wlb-track {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
    height: 100%;
    gap: 2rem;
    padding: 0 2rem;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    touch-action: pan-x;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.wlb-track::-webkit-scrollbar { display: none; }

/* Each slide is fitted to the stage by its own aspect (set on the element from
   the image's natural size in JS), like the Play lightbox: it fills the
   available box on its tight axis and scales the image up to match — so a
   low-res thumbnail fills the frame while the full-res image loads in behind it.
   --avail-w leaves the 2rem side gap; the 1.5 fallback holds before load. */
.wlb-slide {
    --avail-w: calc(100cqw - 4rem);
    --avail-h: 100cqh;
    flex: 0 0 auto;
    width: min(var(--avail-w), calc(var(--avail-h) * var(--wlb-aspect, 1.5)));
    height: min(var(--avail-h), calc(var(--avail-w) / var(--wlb-aspect, 1.5)));
    display: flex;
    align-items: center;
    justify-content: center;
    scroll-snap-align: center;
}

.wlb-img {
    width: 100%;
    height: 100%;
    object-fit: contain; /* slide already matches the image aspect → fills it */
    display: block;
    border: 1px solid var(--text-color);
    user-select: none;
}

/* Controls mirror the Play lightbox: circular buttons on the sides on wide
   screens, dropping to the bottom on mobile. The Share button shares the right
   chevron's slot and replaces it once the last image is reached. */
.wlb-nav,
.wlb-share {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 3.5rem;
    height: 3.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--text-color);
    border-radius: 50%;
    background: var(--bg-color);
    color: var(--text-color);
    cursor: pointer;
    z-index: 210;
}

.wlb-prev { left: 1rem; }
.wlb-next,
.wlb-share { right: 1rem; }

/* Share expands to a labelled pill on the last image (where the next chevron has
   gone), mirroring the Play lightbox. Icon-only on phones. */
.wlb-share {
    width: auto;
    gap: 0.5rem;
    padding: 0 1.4rem;
    border-radius: 1.75rem;
}

.wlb-share-label {
    font-family: var(--font-body);
    font-size: calc((1.15rem) * var(--font-scale));
    line-height: 1;
    white-space: nowrap;
}

@media (max-width: 640px) {
    .wlb-nav,
    .wlb-share {
        top: auto;
        bottom: 1rem;
        transform: none;
    }
}

.wlb-share.success {
    background: var(--text-color);
    color: var(--bg-color);
}

.wlb-dots {
    position: absolute;
    bottom: 1.4rem;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 7px;
    z-index: 5;
}

@media (max-width: 640px) {
    /* Sit level with the bottom chevron row, centred between them. */
    .wlb-dots { bottom: 2.3rem; }
}

.wlb-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    border: 1px solid var(--text-color);
    background: transparent;
}

.wlb-dot.active {
    background: var(--text-color);
}

/* Close button: a bordered circle (like the Play close) sitting exactly where
   the Work search/filter toggle is — centred 2rem from the top-right edge. */
.wlb-close {
    position: absolute;
    top: calc(2rem - 22px);
    right: calc(2rem - 22px);
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--text-color);
    background: var(--bg-color);
    border-radius: 50%;
    cursor: pointer;
    color: var(--text-color);
    z-index: 5;
}

.wlb-close .ms-icon { font-size: 26px; }
.wlb-share .ms-icon { font-size: 26px; }

@media (prefers-reduced-motion: reduce) {
    .work-lightbox,
    .work-lightbox-stage,
    .project-gallery-track,
    .wlb-track { transition: none; }
}

.project-info {
    flex: 999 1 220px;
    /* Let the prose run longer when space permits, capped at a comfortable
       reading measure (~72ch) so wide monitors don't produce unreadable lines. */
    max-width: 72ch;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    justify-content: center;
}

.project-title-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.project-info h2 {
    font-family: var(--font-heading);
    font-size: calc((clamp(1.2rem, 3vw, 2rem)) * var(--font-scale));
    font-weight: 600;
    color: var(--text-color);
}

.share-btn {
    border: none;
    background: transparent;
    cursor: pointer;
    font-size: calc((1rem) * var(--font-scale));
    padding: 0;
    color: var(--text-color);
    position: relative;
}

.share-btn .ms-icon {
    font-size: calc((1.3rem) * var(--font-scale));
}

.share-btn.copied::after {
    content: attr(data-tooltip);
    position: absolute;
    left: 50%;
    bottom: 100%;
    transform: translateX(-50%);
    padding: 0.2rem 0.5rem;
    font-family: var(--font-body);
    font-size: calc((0.8rem) * var(--font-scale));
    background: var(--text-color);
    color: var(--bg-color);
    white-space: nowrap;
    pointer-events: none;
}

.project-meta {
    display: flex;
    gap: 1rem;
    font-family: var(--font-body);
    font-size: calc((clamp(0.8rem, 2vw, 1rem)) * var(--font-scale));
    color: var(--text-color);
}

.project-meta dd {
    margin: 0;
}

.project-info p {
    font-family: var(--font-body);
    font-size: calc((clamp(0.9rem, 2vw, 1.1rem)) * var(--font-scale));
    line-height: 1.4;
    color: var(--text-color);
}

/* Sidebar Column */
.sidebar-column {
    position: absolute;
    top: 2rem;
    /* The 44px filter toggle (and the year timeline below it) are centred in
       this column. On the widescreen layout, pull the column's right edge in by
       half the toggle width so that centre lands on the shared --content-inset
       line — the same vertical line the header dark-mode toggle and the Play
       view toggle sit on. On the normal desktop layout (--content-inset clamps
       to 2rem) that would leave the aside almost touching the category edge, so
       floor the offset at 2rem to match the 2rem gutter used everywhere else.
       max() of two continuous functions keeps the transition graceful. */
    right: max(2rem, calc(var(--content-inset) - 22px));
    height: calc(100% - 4rem);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0;
    z-index: 100;
    pointer-events: none; /* Let clicks pass through empty space */
}

.sidebar-column > * {
    pointer-events: auto; /* Re-enable for spacer and timeline */
}

/* Timeline Sidebar */
.year-timeline {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    overflow-y: auto;
    padding: 0.5rem 0;
    margin: -0.5rem 0;
}

.year-timeline a {
    display: block;
    font-family: var(--font-body);
    font-size: calc((1.1rem) * var(--font-scale));
    color: var(--text-color);
    text-decoration: none;
    padding: 0.5rem 0;
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.year-timeline a.active {
    opacity: 1;
    transform: scale(1.2);
    color: var(--text-color);
    font-weight: 700;
}

/* Compact layout — single breakpoint shared with the Thoughts search and the
   content padding/timeline changes. Below it there is no room for the filter
   bar to sit beside the image column, so the timeline is dropped and the bar
   becomes a full-width overlay that the first project scrolls clear of (the
   scroll headroom is added in work.js, gated on the same 1024px query). */
@media (max-width: 1024px) {
    .year-timeline {
        display: none;
    }

    #work-content .content-viewport {
        display: flex;
        flex-direction: column;
        gap: 0;
    }

    .sidebar-column {
        position: absolute;
        top: 2rem;
        left: 2rem;
        right: 2rem;
        width: auto;
        height: 0;
        margin: 0;
        padding: 0;
        z-index: 100;
        pointer-events: none;
        container-type: inline-size;
    }

    .filter-spacer {
        position: absolute;
        right: -22px; /* Center horizontally on the padding edge / image right corner */
        top: -22px; /* Center vertically on the image top edge */
        margin: 0;
        pointer-events: auto;
    }

    .work-filter {
        max-width: 100cqw !important;
    }

    .work-filter.collapsed {
        max-width: 0 !important;
    }

    .filter-controls {
        width: calc(100cqw - 2rem); /* Expand controls to fill the pill minus padding */
    }

    .work-filter input {
        width: 100%;
        flex: 1;
        min-width: 0;
        max-width: none;
    }
}

