/* Lightbox component - see README.md for the public API and lightbox.js for
   the engine. Design system: shipped in @layer ds.components (unlayered product CSS wins), except the
   dialog box resets just below, with the design-system tokens behind every public --lightbox-* name. The scrim is intentionally near-black in BOTH themes: photos
   read best on a dark neutral ground.

   DROP-IN: every colour and radius below is a custom property with a literal
   fallback, so this file styles itself in a project that defines none of this
   application's tokens. Override a PUBLIC name (--lightbox-*, listed in
   README.md) anywhere up the tree - :root is enough; the private --lb-* names
   are what the rules read, each one the public name, else the app's token,
   else a literal. */
/* Host-proofing, outside the cascade layer on purpose (as upstream): a product's generic dialog { }
   rules - a padding, a max-width, an entrance animation - must not reach the viewer, which is the whole
   viewport. Restyle the viewer through the --lightbox-* properties or a rule on .lightbox-overlay. */
.lightbox-overlay {
    /* A <dialog> in the top layer (lightbox.js: showModal). The UA sheet gives
       a modal dialog a border, a padding, Canvas colours and a max size of the
       viewport minus a margin; all of it goes, because the viewer IS the
       viewport. No z-index: the top layer is above everything already. */
    position: fixed;
    inset: 0;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    max-width: none;
    max-height: none;
    margin: 0;
    border: 0;
    padding: 2rem;
    background: transparent;
    color: inherit;
    overflow: hidden;
    /* The scrim sits at z-index -1 INSIDE this context, under every part of
       the viewer and above nothing of the page's. */
    isolation: isolate;
}

/* Only while open: display on the class alone would beat the UA's
   dialog:not([open]) { display: none } and paint a closed viewer. */
.lightbox-overlay[open] {
    /* Animate viewer layers, never the host's generic dialog or fixed controls. */
    animation: none;
    transform: none;
    transition: none;
    opacity: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}

.lightbox-overlay::backdrop {
    background: transparent;
}

@layer ds.tokens, ds.components;
@layer ds.components {


.lightbox-overlay {
    --lb-scrim: var(--lightbox-scrim, rgba(10, 10, 12, 0.88));
    --lb-surface: var(--lightbox-surface, var(--ds-color-surface, #fff));
    --lb-on-surface: var(--lightbox-on-surface, var(--ds-color-text, #1b1b1b));
    --lb-muted: var(--lightbox-muted, var(--ds-color-text-muted, #555));
    --lb-hover: var(--lightbox-hover, var(--ds-color-neutral-hover, rgba(0, 0, 0, 0.06)));
    --lb-on-scrim: var(--lightbox-on-scrim, #fff);
    --lb-on-scrim-soft: var(--lightbox-on-scrim-soft, rgba(255, 255, 255, 0.8));
    --lb-on-scrim-faint: var(--lightbox-on-scrim-faint, rgba(255, 255, 255, 0.12));
    --lb-tile-bg: var(--lightbox-tile-bg, rgba(255, 255, 255, 0.06));
    --lb-scrollbar: var(--lightbox-scrollbar, rgba(255, 255, 255, 0.28));
    --lb-ring: var(--lightbox-ring, rgba(255, 255, 255, 0.9));
    --lb-focus: var(--lightbox-focus, #fff);
    --lb-page: var(--lightbox-page, #fff);
    --lb-video-bg: var(--lightbox-video-bg, #000);
    --lb-accent: var(--lightbox-accent, var(--ds-color-primary, #057fa3));
    --lb-on-accent: var(--lightbox-on-accent, var(--ds-color-on-primary, #fff));
    --lb-secondary: var(--lightbox-secondary, var(--ds-color-divider, #e0e0e0));
    --lb-radius: var(--lightbox-radius, var(--ds-radius-control, 5px));
}


/* The script hides its layers with the hidden attribute, and several of them
   set their own display (the grid, the doc stage), which beats the UA's
   [hidden] rule. This application's global stylesheet has an !important one;
   a page that drops this file in does not, and there the grid stayed painted
   under the big view (measured in a bare harness). Scoped to the
   viewer, so it changes nothing outside it. */
.lightbox-overlay [hidden] {
    display: none !important;
}

/* Its own element so the script can fade it by OPACITY (open, close, the
   drag-down) and never has to know its colour. */
.lightbox-scrim {
    position: absolute;
    inset: 0;
    z-index: -1;
    background: var(--lb-scrim);
}

.lightbox-figure {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    max-width: min(90vw, 1100px);
    max-height: 90vh;
    margin: 0;
}

.lightbox-image {
    max-width: 100%;
    max-height: calc(90vh - 6rem);
    object-fit: contain;
    border-radius: var(--lb-radius);
    /* All gestures (swipe, pinch, double-tap-drag, pan) are handled in
       lightbox.js - hand every touch to the pointer-event engine. */
    touch-action: none;
    cursor: zoom-in;
    user-select: none;
    -webkit-user-select: none;
    -webkit-user-drag: none;
}

.lightbox-image.zoomed {
    cursor: grab;
}

.lightbox-image.zoomed:active {
    cursor: grabbing;
}

/* Neighbor preview during a swipe drag (lightbox.js updatePeek): slides in
   from the incoming side, growing/fading with drag progress. Decorative
   only - never interactive. */
.lightbox-peek {
    position: fixed;
    top: 50%;
    left: 50%;
    /* Same box constraints as .lightbox-image, so at full drag the incoming
       photo is exactly as big as the one it replaces. */
    max-width: min(90vw, 1100px);
    max-height: calc(90vh - 6rem);
    object-fit: contain;
    border-radius: var(--lb-radius);
    opacity: 0;
    pointer-events: none;
    z-index: 1;
}

/* Leave room for the strip so image + thumbs + bar still fit the viewport. */
.lightbox-figure:has(.lightbox-thumbs:not([hidden])) .lightbox-image {
    max-height: calc(90vh - 10rem);
}

/* Thumbnail strip: the current report's photo series, direct jumps within. */
/* The two scrollers that sit on the scrim - the grid and the strip - scroll
   with a dark, thin, quiet bar whatever the page's theme: the scrim is dark in
   both, and a light system bar was the brightest thing on it. No stepper
   arrows either, which in the grid read as the big view's prev/next (owner:
   "arrows are only needed in detail views"; "if the lightbox is
   dark the scrollbar is dark, minimalistic"). A document page keeps its
   theme's own bar, because it sits on the theme's surface. */
.lightbox-grid-scroll,
.lightbox-thumbs {
    color-scheme: dark;
    scrollbar-width: thin;
    scrollbar-color: var(--lb-scrollbar) transparent;
}

.lightbox-thumbs {
    display: flex;
    gap: 0.4rem;
    max-width: 100%;
    overflow-x: auto;
    padding: 0.15rem 0.15rem 0.6rem;
}

.lightbox-thumb {
    flex: 0 0 auto;
    width: 52px;
    height: 52px;
    padding: 0;
    border: 2px solid transparent;
    border-radius: var(--lb-radius);
    background: none;
    cursor: pointer;
    opacity: 0.6;
}

.lightbox-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: inherit;
    display: block;
}

.lightbox-thumb:hover {
    opacity: 0.85;
}

.lightbox-thumb.active {
    opacity: 1;
    /* The scrim is near-black in both themes - a light ring reads best. */
    border-color: var(--lb-ring);
}

/* Context bar: what am I looking at, and where can I act on it */
.lightbox-bar {
    display: flex;
    align-items: center;
    gap: 1rem;
    width: 100%;
    background: var(--lb-surface);
    color: var(--lb-on-surface);
    border-radius: var(--lb-radius);
    padding: 0.6rem 1rem;
}

.lightbox-text {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    flex: 1 1 auto;
    min-width: 0;
}

.lightbox-caption {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lightbox-context {
    font-size: 0.85rem;
    color: var(--lb-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lightbox-counter {
    flex: 0 0 auto;
    font-size: 0.85rem;
    color: var(--lb-muted);
}

.lightbox-link {
    flex: 0 0 auto;
    color: var(--lb-on-surface);
    text-decoration: underline;
    white-space: nowrap;
}

/* While zoomed the scaled image paints over in-flow siblings, so the context
   bar and thumb strip detach to the bottom of the viewport, layered above the
   photo like the fixed buttons. */
.lightbox-overlay:has(.lightbox-image.zoomed) .lightbox-bar {
    position: fixed;
    left: 50%;
    bottom: 1rem;
    transform: translateX(-50%);
    width: min(90vw, 1100px);
    z-index: 2;
}

.lightbox-overlay:has(.lightbox-image.zoomed) .lightbox-thumbs {
    position: fixed;
    left: 50%;
    bottom: 4.6rem;
    transform: translateX(-50%);
    z-index: 2;
    max-width: min(90vw, 1100px);
}

/* Immersive mode (lightbox.js): while zoomed - or after a single tap on the
   photo - the chrome fades away so only the image remains (the fade itself
   is in the motion block at the end). */
.lightbox-overlay.chrome-hidden .lightbox-close,
.lightbox-overlay.chrome-hidden .lightbox-back,
.lightbox-overlay.chrome-hidden .lightbox-prev,
.lightbox-overlay.chrome-hidden .lightbox-next,
.lightbox-overlay.chrome-hidden .lightbox-bar,
.lightbox-overlay.chrome-hidden .lightbox-thumbs {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

.lightbox-close,
.lightbox-prev,
.lightbox-next {
    position: fixed;
    /* Above the image: the connected-transition transform promotes
       .lightbox-image to its own stacking context, which would otherwise
       paint over the close/prev buttons that precede the figure in the DOM. */
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: var(--lb-surface);
    color: var(--lb-on-surface);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
}

/* The existing Fluent chevrons avoid font baseline and glyph-metric offsets. Close draws Fluent's
   dismiss the same way: its old text glyph (U+2715) is in no common UI font, so the first open paid
   for a system font fallback inside showModal()'s layout (46 ms of a 150 ms click on the phone profile). */
.lightbox-close svg,
.lightbox-prev svg,
.lightbox-next svg {
    display: block;
    flex: none;
    width: 20px;
    height: 20px;
    pointer-events: none;
}

.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover {
    background-image: linear-gradient(var(--lb-hover), var(--lb-hover));
}

.lightbox-close {
    top: 1rem;
    right: 1rem;
}

/* "Overview": the way back from a photo to the grid it was opened on, said
   in words (the close button did the same step, and nobody could tell). Only
   there - a photo opened straight from the page has no grid to go back to. */
.lightbox-back {
    display: none;
    position: fixed;
    top: 1rem;
    left: 1rem;
    z-index: 2;
    align-items: center;
    min-height: 44px;
    padding: 0 1.1rem;
    border: none;
    border-radius: 999px;
    background: var(--lb-surface);
    color: var(--lb-on-surface);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

.lightbox-overlay.from-grid:not(.grid-mode) .lightbox-back {
    display: inline-flex;
}

.lightbox-back:hover {
    background-image: linear-gradient(var(--lb-hover), var(--lb-hover));
}

.lightbox-prev {
    left: 1rem;
    top: 50%;
    transform: translateY(-50%);
}

.lightbox-next {
    right: 1rem;
    top: 50%;
    transform: translateY(-50%);
}

/* A photo that is slow to arrive says so (lightbox.js setLoading, only past
   150ms, so a cached photo never flashes it). */
.lightbox-loading {
    display: none;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    margin: 0;
    color: var(--lb-on-scrim-soft);
    font-size: 0.875rem;
    pointer-events: none;
}

.lightbox-overlay.is-loading .lightbox-loading {
    display: block;
}

/* An <img> keeps painting its PREVIOUS picture until the new one decodes, so
   a slow step showed photo 1 under "Image 9/12" with the note across it
   (measured). Past the 150ms grace the stale picture goes; the note stands
   alone until the right one arrives. */
.lightbox-overlay.is-loading .lightbox-image {
    /* Opacity, not visibility: a hidden element takes no pointer, and every
       gesture (swipe, pull to close) is bound to this image - both were found
       dead while a photo loaded. !important over the inline opacity the
       swipe and the morph write. */
    opacity: 0 !important;
}

/* The grid shows no single photo, so it has no photo to wait for. */
.lightbox-overlay.grid-mode .lightbox-loading {
    display: none;
}

/* The polite live region (lightbox.js announce): read, never seen. */
.lightbox-live {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* FOCUS. A ring the scrim can carry, and one a card surface can carry. */
.lightbox-overlay :focus-visible {
    outline: 2px solid var(--lb-focus);
    outline-offset: 2px;
}

.lightbox-bar :focus-visible,
.lightbox-card :focus-visible,
.lightbox-textdoc:focus-visible,
.lightbox-html :focus-visible {
    outline-color: var(--lb-on-surface);
}

/* Inside a scroller an outside ring is cut by the scroller's own overflow -
   the strip's top and bottom, the grid's first row. These draw INSIDE their
   box, over the picture, which is where nothing clips them. */
.lightbox-overlay .lightbox-thumb:focus-visible,
.lightbox-overlay .lightbox-tile:focus-visible {
    outline-offset: -2px;
}

@media (max-width: 768px) {
    .lightbox-overlay {
        padding: 0.5rem;
    }

    .lightbox-bar {
        flex-wrap: wrap;
        gap: 0.4rem;
    }

    .lightbox-prev {
        left: 0.25rem;
    }

    .lightbox-next {
        right: 0.25rem;
    }
}

/* ---------- gallery grid (lightbox.js: GALLERY MODE) ----------------------
   The whole set at a glance. Shown INSTEAD of the figure - .grid-mode on the
   overlay hides the single-image chrome rather than the two layers competing
   for the same box.

   Sizes are custom properties because the virtualiser READS them
   (layoutGrid getComputedStyle): the tile size lives here with every other
   size in this component, and the script holds no copy of it. A project
   reusing this file changes the look by overriding these two, and the columns
   follow by themselves. */
.lightbox-grid {
    --lightbox-tile: 360px;
    --lightbox-tile-gap: 16px;
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    /* The morph scales this whole layer, so it must scale about its own
       centre or the grid appears to fly in from a corner. */
    transform-origin: center center;
}

/* Large previews keep the scene readable before a person opens detail.
   The virtualiser reads these values; hosts can override the same contract. */
@media (min-width: 900px) {
    .lightbox-grid {
        --lightbox-tile: 440px;
    }
}

.lightbox-overlay.grid-mode .lightbox-figure,
.lightbox-overlay.grid-mode .lightbox-prev,
.lightbox-overlay.grid-mode .lightbox-next,
.lightbox-overlay.grid-mode .lightbox-peek {
    display: none;
}

.lightbox-grid-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    /* Right padding clears the close button, which is 44px wide and sits
       16px from the edge - measured, not guessed: at 3.25rem the count ran
       the last 8px under the button. */
    padding: 1rem 4.25rem 0.75rem 1rem;
    color: var(--lb-on-scrim);
    /* The bar shares the grid's pull-to-close gesture at the top
       (LightboxGallery.bindGridDismiss); a pull must not select its words. */
    user-select: none;
    -webkit-user-select: none;
}

.lightbox-grid-title {
    font-weight: 600;
    /* One line, because a three-line caption would push the grid down on a
       phone - the one place the rows are scarcest. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lightbox-grid-count {
    flex: none;
    opacity: 0.75;
    font-size: 0.9rem;
}

.lightbox-grid-scroll {
    flex: 1 1 auto;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 0 var(--lightbox-tile-gap) var(--lightbox-tile-gap);
    /* The scroller is focusable (it is the thing a keyboard scrolls), so it
       needs a visible ring like any other focusable element - drawn inside,
       because the grid layer's edge is the viewport's. */
    outline-offset: -3px;
    /* Momentum scrolling on iOS, and no rubber-banding of the page behind. */
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
}

.lightbox-overlay .lightbox-grid-scroll:focus-visible {
    outline-offset: -3px;
}

/* Position: relative is load-bearing - every tile is absolutely positioned
   against this one box, which is what lets the virtualiser add and remove
   tiles without moving anything already on screen. Its height is set by the
   script to the FULL set, so the scrollbar describes the real list. */
.lightbox-grid-spacer {
    position: relative;
    width: 100%;
}

.lightbox-tile {
    position: absolute;
    top: 0;
    left: 0;
    padding: 0;
    border: none;
    border-radius: var(--lb-radius);
    overflow: hidden;
    background-color: var(--lb-tile-bg);
    cursor: pointer;
    /* Inside the box: the first row touches the scroller's top edge, which
       would cut a ring drawn outside it. */
    outline: 2px solid transparent;
    outline-offset: -2px;
}

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

.lightbox-tile:hover img,
.lightbox-tile:focus-visible img {
    opacity: 0.85;
}

/* The tile the big view was opened from, so a person coming back sees where
   they were. */
.lightbox-tile.active {
    outline-color: var(--lb-ring);
}

/* ---------- members that are not images (lightbox.js: NOT ONLY IMAGES) -----
   A document gets a PAGE, not a floating object: the figure takes its full
   width so a PDF or a text reads at a line length, and the page is the card
   surface of the theme so it reads the way the same text reads in the app. */
.lightbox-overlay.is-doc .lightbox-figure {
    width: min(90vw, 1100px);
}

.lightbox-doc {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    min-height: 0;
}

.lightbox-doc-stage {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    min-height: 0;
}

.lightbox-frame {
    display: block;
    width: 100%;
    height: calc(90vh - 6rem);
    border: 0;
    border-radius: var(--lb-radius);
    background: var(--lb-page);
}

.lightbox-video {
    display: block;
    max-width: 100%;
    max-height: calc(90vh - 6rem);
    border-radius: var(--lb-radius);
    background: var(--lb-video-bg);
}

.lightbox-textdoc,
.lightbox-html {
    box-sizing: border-box;
    width: 100%;
    max-height: calc(90vh - 6rem);
    margin: 0;
    overflow: auto;
    overscroll-behavior: contain;
    padding: 1.25rem 1.5rem;
    border-radius: var(--lb-radius);
    background: var(--lb-surface);
    color: var(--lb-on-surface);
    text-align: left;
}

.lightbox-textdoc {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
    font-size: 0.875rem;
    line-height: 1.5;
}

/* Room for the switch above the page. */
.lightbox-doc:has(.lightbox-doc-switch) :is(.lightbox-textdoc, .lightbox-html) {
    max-height: calc(90vh - 9rem);
}

.lightbox-doc-note {
    margin: 0;
    color: var(--lb-on-scrim-soft);
    font-size: 0.875rem;
}

/* Formatted | Original: a segmented control, on the scrim like the grid's
   head, so it reads as the viewer's own chrome rather than part of the file. */
.lightbox-doc-switch {
    display: inline-flex;
    align-self: center;
    padding: 0.2rem;
    gap: 0.2rem;
    border-radius: 999px;
    background: var(--lb-on-scrim-faint);
}

.lightbox-doc-switch button {
    min-height: 2.25rem;
    padding: 0 1rem;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--lb-on-scrim);
    font: inherit;
    font-size: 0.875rem;
    cursor: pointer;
}

.lightbox-doc-switch button[aria-pressed="true"] {
    background: var(--lb-surface);
    color: var(--lb-on-surface);
    font-weight: 600;
}

/* The honest fallback: what the file is and how to get it. */
.lightbox-card {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    width: min(100%, 24rem);
    padding: 2rem 1.5rem;
    border-radius: var(--lb-radius);
    background: var(--lb-surface);
    color: var(--lb-on-surface);
    text-align: center;
}

.lightbox-card-glyph {
    width: 3rem;
    height: 3rem;
    color: var(--lb-muted);
    /* A picture, never a target (lightbox.js fileCard scrubs it as well). */
    pointer-events: none;
}

/* Every part of it too: pointer-events inherits, and a child could ask for
   clicks back. */
.lightbox-card-glyph * {
    pointer-events: none !important;
}

.lightbox-card-name {
    overflow-wrap: anywhere;
}

.lightbox-card-note {
    color: var(--lb-muted);
    font-size: 0.875rem;
}

.lightbox-card-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
    margin-top: 0.5rem;
}

.lightbox-card-actions a {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    padding: 0 1.1rem;
    border-radius: 999px;
    background: var(--lb-accent);
    color: var(--lb-on-accent);
    text-decoration: none;
    font-weight: 600;
}

.lightbox-card-actions a + a {
    background: var(--lb-secondary);
    color: var(--lb-on-surface);
}

.lightbox-download {
    flex: 0 0 auto;
    color: var(--lb-on-surface);
    text-decoration: underline;
    white-space: nowrap;
}

/* Said once: where a card is showing, its own button downloads, so the bar
   drops its link (a PDF shows its card only on a phone, below). */
.lightbox-overlay:has(.lightbox-doc:not(.is-pdf) .lightbox-card) .lightbox-download {
    display: none;
}

/* A PDF: the frame where the browser can fill one, the card on a phone, where
   Chrome on Android paints a frame as a blocked-document plate. A viewport
   query on purpose - the question is what the BROWSER renders, the same one
   the file explorer's preview answers at the same width. */
.lightbox-doc.is-pdf .lightbox-card {
    display: none;
}

@media (max-width: 768px) {
    .lightbox-doc.is-pdf .lightbox-frame {
        display: none;
    }

    .lightbox-doc.is-pdf .lightbox-card {
        display: flex;
    }

    .lightbox-overlay:has(.lightbox-doc.is-pdf) .lightbox-download {
        display: none;
    }

    .lightbox-textdoc,
    .lightbox-html {
        padding: 1rem;
    }
}

/* ---------- motion -----------------------------------------------------------
   Every CSS transition, and only for a reader who has not asked for less. The
   script's own animations (the morphs, the swipe, the drag) check the same
   query in prefersReducedMotion(). */
@media (prefers-reduced-motion: no-preference) {
    /* Only for the double-tap/-click toggle; live gestures track 1:1. */
    .lightbox-image.zoom-anim {
        transition: transform 0.2s cubic-bezier(0.2, 0, 0, 1);
    }

    .lightbox-thumb {
        transition: opacity 0.15s ease, border-color 0.15s ease;
    }

    .lightbox-close,
    .lightbox-back,
    .lightbox-prev,
    .lightbox-next,
    .lightbox-bar,
    .lightbox-thumbs {
        transition: opacity 0.2s ease, visibility 0.2s;
    }

    /* Only opacity and outline: a transform transition would fight the
       translate() the virtualiser uses for placement. */
    .lightbox-tile {
        transition: opacity 120ms ease, outline-color 120ms ease;
    }
}

/* ---------- forced colours (Windows High Contrast and the like) --------------
   The system repaints every colour above; what it cannot know is which edges
   carried meaning. A round button, a card and the active thumbnail were told
   apart by fill alone, so they get a system-coloured edge. */
@media (forced-colors: active) {
    .lightbox-close,
    .lightbox-back,
    .lightbox-prev,
    .lightbox-next,
    .lightbox-doc-switch {
        border: 1px solid ButtonText;
    }

    .lightbox-bar,
    .lightbox-card,
    .lightbox-textdoc,
    .lightbox-html {
        border: 1px solid CanvasText;
    }

    .lightbox-thumb.active {
        border-color: Highlight;
    }

    .lightbox-tile.active {
        outline-color: Highlight;
    }

    .lightbox-overlay :focus-visible {
        outline-color: Highlight;
    }

    .lightbox-doc-switch button[aria-pressed="true"] {
        forced-color-adjust: none;
        background: Highlight;
        color: HighlightText;
    }
}

/* Triggers written by renderLightboxTrigger() (lightbox.php): a link to the file with its thumbnail,
   and "+N" for the photos behind the cover. The host lays them out (a grid, a table cell). */
:where(.ds-lightbox-trigger) {
    position: relative;
    display: inline-block;
    border-radius: var(--ds-radius-control);
    color: var(--ds-color-primary);
    &:focus-visible {
        outline: var(--ds-focus-width) solid var(--ds-color-focus);
        outline-offset: var(--ds-focus-offset);
    }
    & > .ds-lightbox-thumb {
        display: block;
        inline-size: 100%;
        aspect-ratio: 4 / 3;
        object-fit: cover;
        border-radius: inherit;
        background: var(--ds-color-disabled-surface);
    }
    & > .ds-lightbox-more {
        position: absolute;
        inset-block-end: var(--ds-space-2);
        inset-inline-end: var(--ds-space-2);
        padding-inline: var(--ds-space-2);
        border-radius: 999px;
        background: color-mix(in srgb, var(--ds-color-text) 82%, transparent);
        color: var(--ds-color-surface);
        font: 600 var(--ds-type-control-small) / 1.6 var(--ds-font);
    }
}
@media (forced-colors: active) {
    :where(.ds-lightbox-trigger) > .ds-lightbox-more { border: 1px solid CanvasText; }
}
}
