.carousel-container {
    /* No margin: .generation owns the spacing through `gap`. Keeping both meant
       every bubble silently claimed 20px more than its width, so a row that
       arithmetically fitted wrapped anyway — five 260px bubbles needed 1528px
       of a 1440px row. */
    margin: 0;
    position: relative;

    max-width: 100%;
    max-height: 300px;
    height: fit-content ;

    width: 100%;
    aspect-ratio: 1 / 1;
}

.carousel-title-container {
    z-index: var(--z-chrome);
    width: 70%;
    left:15%;
    position: relative;
    top: 78%;
    height: 50%
}

.carousel-title {
    margin-bottom: 10px;
    font-size: 1.5rem;
    text-align: center;
}

.carousel {
    display: flex;
    overflow: visible;
    position: relative;
    border-radius: 100%;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    cursor: pointer;
    background-color: #f5f5f5;
    height: 100%;
    width: 100%;
}

.carousel-item {
    min-width: 100%;
    transition: transform var(--motion-slow) ease-in-out,
                filter var(--motion-slow) ease-in-out,
                opacity var(--motion-slow) ease-in-out;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    opacity: 0;
    border-radius: 100%;
    pointer-events: none;
    filter: brightness(1.0);
}

.carousel-item.active {
    opacity: 1;
    pointer-events: auto;
    z-index: var(--z-card);
}


/* will-change is a just-in-time hint. Left on permanently it promoted all 43
   photos to their own compositor layer for nothing. */
.carousel:hover .carousel-item {
    will-change: transform, opacity;
    filter: brightness(1.2);
    cursor: pointer;
    transform: scale(1.05) translateZ(0);
    /* The highlight — the hovered photo brightening and growing — must be quick:
       it is the immediate "you've picked this one" feedback. Only the OTHER dogs
       creep away slowly, and that lives on .is-dimmed, not here. Deliberately no
       delay and a short duration; an earlier version borrowed the spotlight's
       0.5s delay and slow duration, which made the highlight itself sluggish.
       (transform/filter only; opacity is the photo cross-fade, left untouched.) */
    transition: transform var(--motion-base) var(--ease-out),
                filter var(--motion-base) var(--ease-out);
}

.carousel-img-container {
    width: 90%;
    height: 90%;
    border-radius: 50%;
    overflow: hidden;
    position: relative;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
    border: 2px solid black;
}

.carousel-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Per-photo framing, set as custom properties by carouselBuilder.
       --focus-tablet and --focus-phone override this at the narrower tiers
       (see css/phone.css).

       object-position pans; it cannot zoom, because `cover` already scales the
       image to fill. So zoom is a transform on top, anchored to the SAME point
       the framing aims at — scaling from the focus keeps the face where it was
       put instead of drifting toward the centre. .carousel-img-container clips
       with overflow:hidden, so the scaled image stays inside the circle. */
    object-position: var(--focus, 50% 50%);
    transform: scale(var(--zoom, 1));
    transform-origin: var(--focus, 50% 50%);
}

.carousel-caption {
    margin-top: 15px;
    color: #333;
    font-weight: bold;
    text-align: center;
}

/* Zoom modal styles */
.zoom-modal {
    visibility: hidden;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.9);
    z-index: var(--z-modal);
    display: flex;
    justify-content: center;
    align-items: center;
    opacity: 0;
    transition: opacity var(--motion-slow) ease-out, visibility var(--motion-slow);
}

.zoom-modal.active {
    will-change: opacity;
    visibility: visible;
    opacity: 1;
    transform: scale(1);
}

/* Zoom container animation */
.zoom-container {
    max-width: 90%;
    max-height: 90%;
    position: relative;
}

.zoom-modal.active .zoom-container {
    transform: translateY(0);
}

.zoom-image.active {
    opacity: 1 !important;
}

/* The photo and its two paw controls sit on one row, so the paws are ALWAYS
   beside the image rather than on top of it. They used to be absolutely
   positioned against the container, which is as wide as the screen — so a
   portrait photo left them outside but a landscape photo put them over it. */
.zoom-image-container {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(0.5rem, 2vw, 1.5rem);
}

/* Lets the two buttons become items of the row above without touching the
   markup, which is shared with the generated pages. */
.zoom-nav {
    display: contents;
}

#zoomPrev { order: -1; }
#zoomNext { order: 1; }

.image-container {
    display: grid;
    justify-items: center;
    align-items: center;
    position: relative;
    min-width: 0;      /* so the photo, not the row, is what shrinks */
}

/* Two <img> elements share one grid cell to cross-fade between photos, which
   means the cell — and therefore the paws beside it — was sized by whichever
   of the two was WIDER. Going from a landscape photo to a portrait one left
   the paws stranded far out. The outgoing image is taken out of flow so only
   the photo actually on screen decides the width. */
.zoom-image.hidden {
    position: absolute;
    inset: 0;
    margin: auto;
}

/* Zoom image with fade effect */
.zoom-image {
    grid-column: 1;
    grid-row: 1;
    max-width: 100%;
    max-height: 80vh;
    display: block;
    border-radius: 10px;
    opacity: 0;
    transform: translateY(20px);
    transition: opacity 0.4s ease-in-out;
}


/* Close button smooth hover */
.zoom-close {
    position: absolute;
    top: -40px;
    right: 0;
    color: white;
    font-size: 4REM;
    cursor: pointer;
    background: none;
    border: none;
    transition: transform 0.3s ease-out;
}

.zoom-close:hover {
    transform: rotate(90deg);
}

/* Navigation arrows with subtle hover effects */


.zoom-caption {
    color: white;
    text-align: center;
    padding: 22px 30px 10px;
    font-size: 1.2rem;
}

/* Position in the set, under the name — the same paw row that sits above each
   bubble. The paw artwork is black and the backdrop is nearly black, so every
   paw in this modal is inverted to white. */
.zoom-dots {
    display: flex;
    justify-content: center;
    gap: 0.4rem;
    padding-bottom: 26px;
}

.zoom-dots[hidden] {
    display: none;
}

.zoom-dot {
    border: 0;
    padding: 0.2rem;
    background: none;
    line-height: 0;
    cursor: pointer;
    border-radius: 50%;
    -webkit-appearance: none;
    appearance: none;
}

.zoom-dot img {
    width: 0.85rem;
    height: 0.85rem;
    display: block;
    /* brightness(0) flattens the artwork to black, invert(1) lifts it to white. */
    filter: brightness(0) invert(1) opacity(0.35);
    transform: scale(0.85);
    transition: filter var(--motion-base) var(--ease-out),
                transform var(--motion-base) var(--ease-out);
}

.zoom-dot:hover img {
    filter: brightness(0) invert(1) opacity(0.7);
    transform: scale(1);
}

.zoom-dot.is-current img {
    filter: brightness(0) invert(1);
    transform: scale(1.15);
}

.zoom-dot:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 2px;
}

.zoom-arrow {
    flex: none;
    width: 50px;
    height: 50px;
    background-color: rgba(255, 255, 255, 0.2);
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    transition: background-color 0.3s;
}

.zoom-arrow:hover {
    background-color: rgba(255, 255, 255, 0.4);
}

.zoom-arrow img {
    width: 30px;
    height: 30px;
    filter: brightness(0) invert(1);   /* white on the dark backdrop */
}

svg {
    pointer-events: none;
}
/* Arrows and the bubble are real controls now; strip the UA button styling and
   give focus something visible. The ring picks up the dog's own colour. */
.rotate-270 { transform: rotate(270deg); }
.rotate-90  { transform: rotate(90deg); }

.carousel:focus-visible,
.carousel-dot:focus-visible,
.zoom-arrow:focus-visible,
.zoom-close:focus-visible {
    outline: 3px solid var(--focus-ring, var(--heart-1));
    outline-offset: 3px;
}

.carousel:focus:not(:focus-visible) {
    outline: none;
}

.zoom-arrow {
    border: 0;
    padding: 0;
    font: inherit;
    -webkit-appearance: none;
    appearance: none;
}

/* ---- Photo dots -------------------------------------------------------
   One paw per photo, sitting above the bubble. Replaces the two arrows that
   flanked the name tag: those only offered "next", these show how many
   photos there are, which one you are on, and jump straight to any of them. */
.carousel-dots {
    position: absolute;
    top: -1.15rem;
    /* Sized to its own content and centred over the bubble, rather than
       stretched left-to-right across it. Pinned to both edges, a dog with
       seven photos had its row squeezed: flex shrank each 24px button to
       21.2px and the targets stopped being targets. The row is allowed to be
       wider than the bubble instead — it sits above it, over the gap between
       generations, so there is nothing there to collide with. */
    left: 50%;
    transform: translateX(-50%);
    width: max-content;
    max-width: 100vw;
    display: flex;
    justify-content: center;
    /* No gap: each button is 24 wide and carries its own air. */
    gap: 0;
    z-index: var(--z-chrome);
}

/*
 * The paw you SEE is 0.72rem; the paw you can HIT is 24×24.
 *
 * Spacing alone was not enough here, and the reason is worth keeping: these
 * dots sit ON TOP of the bubble, and the bubble is itself a tap target — it
 * opens the lightbox. So the free space around a dot is not the gap to the
 * next dot, it is nothing at all, because a larger target overlaps it
 * completely. WCAG 2.5.8's spacing exception needs a clear 24px circle, and
 * an overlapping neighbour collapses it to nothing. Lighthouse measured 7px.
 *
 * A 24×24 target satisfies the rule outright, overlap or not, so the button
 * grows and the artwork does not.
 */
.carousel-dot {
    border: 0;
    padding: 0;
    background: none;
    line-height: 0;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    border-radius: 50%;
    width: 1.5rem;
    height: 1.5rem;
    flex: 0 0 auto;          /* never shrink below the target size */
    display: grid;
    place-items: center;
}

.carousel-dot img {
    width: 0.72rem;
    height: 0.72rem;
    display: block;
    filter: opacity(0.22);
    transform: scale(0.82);
    transition: filter var(--motion-base) var(--ease-out),
                transform var(--motion-base) var(--ease-out);
}

.carousel-dot:hover img {
    filter: opacity(0.5);
    transform: scale(1);
}

.carousel-dot.is-current img {
    filter: opacity(0.78);
    transform: scale(1.12);
}

/* ---- Spotlight --------------------------------------------------------
   Hovering a photo makes every other dog fade out completely — greying as it
   goes — until one is left alone on the paper.

   The durations sit on two different rules on purpose. A transition uses the
   duration declared on the state it is moving TO, so the base rule governs
   coming back (quick) and .is-dimmed governs going away (slow). */
.carousel-container {
    transition: opacity var(--motion-return) var(--ease-out),
                filter var(--motion-return) var(--ease-out);
}

.carousel-container.is-dimmed {
    opacity: 0;
    filter: grayscale(1);
    /* Cannot hover what you cannot see: without this, sweeping across an
       invisible bubble would drag the spotlight onto it. */
    pointer-events: none;
    transition: opacity var(--motion-vanish) var(--ease-creep) var(--motion-vanish-delay),
                filter var(--motion-vanish) var(--ease-creep) var(--motion-vanish-delay);
}

/* The connector lines and hearts go with them — left at full strength they
   would point at bubbles that are no longer there. */
#bgCanvas {
    transition: opacity var(--motion-return) var(--ease-out);
}

html.has-spotlight #bgCanvas {
    opacity: 0;
    transition: opacity var(--motion-vanish) var(--ease-creep) var(--motion-vanish-delay);
}

/* ---- Family album -----------------------------------------------------
   The whole-family group photos, shown above the tree as one wide framed
   carousel. Built by the same carousel code as the dog bubbles (so it gets the
   cross-fade, the paw dots and click-to-zoom for free) but reshaped from a
   circle into a rounded rectangle, since these photos would lose their edges in
   a round crop. Scoped entirely to #family-album so nothing else is touched. */
#family-album {
    display: flex;
    justify-content: center;
    /* Extra top room for the paw dots, which sit above the frame. */
    padding: 3.2rem var(--space-md) var(--space-md);
}

#family-album .carousel-container,
.family-poster {
    width: min(92vw, 600px);
    aspect-ratio: 4 / 3;
}

#family-album .carousel-container {
    max-width: none;
    max-height: none;
    margin: 0;
}

#family-album .carousel,
#family-album .carousel-item {
    border-radius: 1.25rem;
}

#family-album .carousel-img-container {
    width: 100%;
    height: 100%;
    border-radius: 1rem;
}

/* Paw dots sit below the frame here — there is no bone tag owning the bottom.
   Bigger than the tree's: on the album they are the ONLY way to change photo
   (no auto-advance), whereas under a bubble they are a secondary hint to a
   carousel that rotates on its own. At the tree's 11px they were neither
   visible nor easy to hit. */
#family-album .carousel-dots {
    top: auto;
    bottom: calc(100% + 0.4rem);   /* just above the top edge of the frame */
    gap: 0.7rem;
}

#family-album .carousel-dot {
    padding: 0.4rem;   /* a real hit target, not an 11px paw */
}

#family-album .carousel-dot img {
    width: 1.75rem;
    height: 1.75rem;
    filter: opacity(0.3);       /* inactive, but clearly present */
    transform: scale(0.9);
}

#family-album .carousel-dot:hover img {
    filter: opacity(0.6);
    transform: scale(1);
}

/* The current photo: full-strength paw, noticeably larger — the selection reads
   at a glance. */
#family-album .carousel-dot.is-current img {
    filter: none;
    transform: scale(1.3);
}

/* The static poster shown when the scripts do not run (file://, no JS). The
   carousel builder clears this container, so it only ever appears as fallback. */
.family-poster {
    display: block;
    margin: 0 auto;
    object-fit: cover;
    border-radius: 1.25rem;
    border: 3px solid rgba(150, 120, 86, 0.8);
    box-shadow: 0 8px 24px rgb(58 47 42 / 18%);
}

/* ---- Placeholder bubble ----------------------------------------------
   A dog with no photos yet: a soft paw silhouette in its colour, standing in
   until the real pictures arrive. Same size and name tag as a real bubble, so
   the tree reads normally. */
.carousel--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: default;
}

.carousel-placeholder-paw {
    width: 42%;
    height: 42%;
    object-fit: contain;
    opacity: 0.35;
    /* Tint the black paw toward the bubble's own colour via the container var. */
    filter: grayscale(0.3);
}

/* ---- Pedigree ---------------------------------------------------------
   A dog with papers links to them, from the tree and from the lightbox.

   On the tree it hangs under the bone name tag — the one place on a bubble
   that already says whose dog this is. It flows inside
   .carousel-title-container, whose height is fixed and overflows visibly, so
   the tree layout and the connector canvas, which both measure
   .carousel-container, never see it. Faint at rest and plain on hover or
   keyboard focus, because on a page of photographs this is a footnote. */
.carousel-pedigree {
    display: block;
    text-align: center;
    margin-top: -0.35rem;
    /* 0.68rem type gave an 18px-tall target; pad to 24 and pull the extra
       back out of the flow, so nothing below it moves. */
    min-height: 1.5rem;
    padding-block: 0.3rem;
    margin-bottom: -0.6rem;
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    color: var(--color-ink-soft);
    text-decoration: none;
    opacity: 0.5;
    transition: opacity var(--motion-base) var(--ease-out);
}

.carousel-pedigree:hover,
.carousel-pedigree:focus-visible {
    opacity: 1;
    text-decoration: underline;
}

/* The lightbox backdrop is nearly black, so this one is written in white
   rather than ink — same link, opposite ground. */
.zoom-pedigree {
    display: block;
    text-align: center;
    padding: 0 30px 14px;
    font-size: 0.85rem;
    color: rgba(255, 255, 255, 0.68);
    text-decoration: underline;
    text-underline-offset: 0.2em;
}

.zoom-pedigree:hover,
.zoom-pedigree:focus-visible {
    color: #fff;
}

.zoom-pedigree[hidden] {
    display: none;
}
