/* ============================================================
   Home carousel — "Recent", the newest things on the site.

   A row of cards, each two DLS columns wide (Figma 730px), shown
   two-up with the next card peeking past the right screen edge. A
   segmented progress pager (top-right, beside the "Recent" title)
   fills over a 7s auto-advance; a right-edge cursor capture turns
   the pointer into a → arrow so a click there advances, while the
   cards themselves stay clickable everywhere else.

   Progressive enhancement: with no JS this is a horizontally
   scrollable row of links (scroll-snapped). scripts/carousel.js
   adds .carousel--enhanced and takes over with a transformed track,
   the pager, auto-advance, loop, pause-on-hover and the cursor.

     <section class="carousel" aria-roledescription="carousel">
       <div class="carousel__head">
         <h1 class="hero__headline">Recent</h1>
         <div class="carousel__progress"></div>        (pager — JS fills)
       </div>
       <div class="carousel__viewport">
         <ul class="carousel__track">
           <li class="carousel__slide">
             <a class="carousel__card" href="…" style="--card-bg:#69766c">
               <span class="carousel__frame"><img class="carousel__shot" …></span>
               <span class="carousel__caption">
                 <span class="carousel__kicker">Essay</span>
                 <span class="carousel__title">Fish longer…</span>
               </span>
             </a>
           </li> …
         </ul>
       </div>
       <div class="carousel__cursor" aria-hidden="true">→</div>
     </section>

   TOKENS — every colour, size, space and radius is a DLS token:
     card = 2 grid columns  → (100% − 2·margin − gutter) / 2  (Figma 730)
     4px inter-card gap     → --grid-gutter
     16px frame → caption   → --space-16
     8px pager pill + gap   → --space-8
     8px corner             → --radius
     30px title             → --text-xl   (title) · 16px kicker → --text-sm
     filled / seen dash     → --fg
     unfilled dash (#d9d9d9)→ --line  (= --black-15)
     caption grey (#86868b) → --muted (the house mapping)

   The lone off-scale value is the coloured frame's inner inset —
   Figma 40px, which sits between --space-32 and --space-48 on the
   named scale; kept as a local constant below.
   ============================================================ */

.carousel {
  /* Card width = exactly two of the page's four grid columns. The basis
     here is the TRACK's content box, which the track's padding-inline-start
     has already inset by one 24px margin — so only the remaining margin +
     the inter-card gutter are subtracted (matching the mobile formula's
     reasoning below). Subtracting two margins would make every card 12px
     narrow, over-revealing the next card past the right edge.
     (100% − 24 margin − 4 gutter) / 2  →  730px at 1512. */
  --card-w: calc((100% - var(--grid-margin) - var(--grid-gutter)) / 2);
  --carousel-frame-pad: 40px;   /* Figma frame inset — off the named 8px scale (32 < 40 < 48) */
  --carousel-dur: 7s;           /* auto-advance interval; mirrored by carousel.js */
}

/* --- Head — the "Recent" title with the pager pinned bottom-right,
   matching the Figma hero row (align to the display baseline). ------ */
.carousel__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-24);
  padding-inline: var(--grid-margin);            /* 24px screen edge margin */
}

/* --- Viewport — clips the track. Its left edge sits on the 24px
   margin, but it bleeds to the right screen edge, so the following
   card peeks in the right margin exactly as in Figma. --------------- */
.carousel__viewport {
  margin-top: var(--rhythm-block);               /* 32px title → track (Figma) */
  /* Full-bleed to BOTH screen edges, so a sliver of the previous card
     peeks on the left exactly as the next card does on the right. The
     track's inline padding + scroll-padding below keep the cards snapped
     to the 24px grid margin, so only the margins reveal the peeks. */
  overflow-x: auto;                              /* the scroller — trackpad, wheel + touch swipe */
  scroll-snap-type: x mandatory;                 /* every settle lands on a card boundary */
  scroll-padding-inline: var(--grid-margin);     /* snap to the 24px grid line, not the screen edge */
  overscroll-behavior-x: contain;                /* keep swipes in the carousel — no trackpad back/forward */
  scrollbar-width: none;                         /* the pager is the affordance */
}
.carousel__viewport::-webkit-scrollbar { display: none; }

/* --- Track — the flex row of slides. The carousel scrolls natively
   (no transform); carousel.js reads scroll position for the pager and
   drives the auto-advance with scrollTo, so trackpad/wheel/touch and
   the auto-advance all move the same real scroll. ------------------ */
.carousel__track {
  display: flex;
  gap: var(--grid-gutter);                       /* 4px between cards */
  list-style: none;
  padding-inline-start: var(--grid-margin);      /* first card rests on the 24px left margin */
}
/* Trailing inset as a real flex item. A scroll container IGNORES a flex
   track's end padding for its scrollable width, so padding-inline-end
   would leave the last card flush to the screen edge — but it DOES honour
   a spacer box. This keeps the last card 24px off the right edge, exactly
   mirroring the left. Being a pseudo-element, it's not one of the slides
   (track.children), so carousel.js's pager/step maths are untouched. */
.carousel__track::after {
  content: "";
  /* The track's `gap` also sits before this spacer, so subtract it: the
     4px gap + this basis = exactly a 24px trailing margin. */
  flex: 0 0 calc(var(--grid-margin) - var(--grid-gutter));
}

.carousel__slide {
  flex: 0 0 var(--card-w);
  scroll-snap-align: start;
}

/* --- Card — coloured frame over a shadowed shot, caption beneath. -- */
.carousel__card {
  display: flex;
  flex-direction: column;
  gap: var(--space-16);                          /* 16px frame → caption */
  text-decoration: none;
  color: inherit;
}

.carousel__frame {
  position: relative;                            /* anchors the .lock badge */
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 730 / 547.5;                     /* Figma tile proportion */
  padding: var(--carousel-frame-pad);            /* 40px coloured inset */
  border-radius: var(--radius);                  /* 8px */
  background: var(--card-bg, var(--surface-sunken));
  overflow: hidden;
}
.carousel__shot {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgb(0 0 0 / 0.15);      /* Figma card shadow */
}

/* Composite covers (Cover Studio) are ALREADY a finished card — the accent
   backdrop, the framed shots and their shadow are baked into the image. Render
   them edge-to-edge so they don't nest inside the frame's own coloured mat: no
   inset, no second radius, no doubled shadow. The frame keeps its 8px radius +
   overflow:hidden, so it clips the cover's corners; the cover is 4:3 like the
   frame, so `cover` fills it with no crop. */
.carousel__frame:has(> .carousel__shot--card) { padding: 0; }
.carousel__shot--card { border-radius: 0; box-shadow: none; }

/* Essay cards are a full PAGE thumbnail (a browser-window screenshot): it sits
   inset on the top and sides but BLEEDS off the bottom of the coloured wrapper,
   with rounded top corners — a page peeking up from the bottom edge.

   This is the standard, battle-tested responsive-cover pattern: an ABSOLUTELY
   positioned image with `inset: 0; width/height: 100%; object-fit: cover`. The
   key is that the image is OUT OF FLOW — so it cannot influence the frame's
   height. Every in-flow (flex) attempt failed because the tall image and the
   `aspect-ratio` frame sized each other in a loop: WebKit let the image stretch
   the frame tall (measured 588px), so the nav ended up centred in an oversized
   box (the gap). Out of flow, the frame keeps its `aspect-ratio` height and the
   image just fills it.

   The coloured inset is a BORDER (not padding) so the absolute image's box —
   which fills the padding box (inside the border) — lands exactly on the inset:
   40px on top and sides, 0 at the bottom so the page bleeds off the bottom edge.
   `object-position: top` pins the nav to the top; the image is authored tall
   (~1.15, see /add-article) so `cover` crops only the BOTTOM (illustration
   bleeds), never the sides — the logo is always safe. */
.carousel__frame:has(> .carousel__shot--page) {
  position: relative;
  padding: 0;
  border: var(--carousel-frame-pad) solid var(--card-bg, var(--surface-sunken));
  border-bottom-width: 0;                        /* page bleeds off the bottom */
}
.carousel__shot--page {
  position: absolute;
  inset: 0;                                      /* fill the box inside the border */
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* --- Caption — kicker over title, tight leading (Figma caption). --- */
.carousel__caption { display: flex; flex-direction: column; }
.carousel__kicker {
  font-size: var(--text-sm);                     /* 16px "Small" */
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--muted);
}
.carousel__title {
  font-size: var(--text-xl);                     /* 30px */
  line-height: var(--lh-xl);                     /* 40px, on the grid */
  letter-spacing: var(--tracking-tight);
  color: var(--fg);
  margin-top: var(--space-4);
}
/* Optional second title line (a metric or subtitle), de-emphasised. */
.carousel__sub { display: block; color: var(--muted); }

/* =============================================================
   Segmented progress pager — one pill per card.
     · unseen  → small, grey (--line), shares the row (flex-grow)
     · seen    → small, black (--fg)
     · active  → fixed 120px, grey track with a black fill that
                 sweeps left→right over --carousel-dur
   ============================================================= */
.carousel__progress {
  display: flex;
  align-items: center;
  gap: var(--space-8);                           /* 8px between pills */
  /* Exactly one grid column wide, so it fills the rightmost (4th) column
     of the page grid. The head's padding-inline has already inset 100%
     by both 24px margins, so a column is (that content box − 3 gutters)/4.
     Floored at 180px so the pills never crush on narrow screens. It sits
     hard-right (the head's space-between) 24px off the edge (head padding),
     landing at ~360px on the 1512 design width. */
  width: max(180px, calc((100% - 3 * var(--grid-gutter)) / 4));
  height: var(--space-8);                        /* 8px tall */
}
.carousel__progress:empty { display: none; }     /* no-JS: nothing to show */

.carousel__dot {
  position: relative;
  flex: 1 1 0;                                   /* small pills share the row */
  min-width: var(--space-8);
  height: var(--space-8);
  border-radius: 999px;                          /* fully-round pill */
  background: var(--line);                       /* unseen */
  overflow: hidden;
  transition: flex-basis 0.55s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.35s ease;
}
.carousel__dot--seen   { background: var(--fg); }
.carousel__dot--active { flex: 0 0 120px; background: var(--line); }   /* Figma active width */

.carousel__dot-fill {
  position: absolute;
  inset: 0;
  width: 0;
  border-radius: inherit;
  background: var(--fg);                          /* the black progress fill */
}
.carousel__dot--active .carousel__dot-fill {
  animation: carousel-fill var(--carousel-dur) linear forwards;
}

@keyframes carousel-fill { from { width: 0; } to { width: 100%; } }

@media (max-width: 640px) {
  /* One card at a time, filling the content width (24px margins), with
     the next card peeking past the right margin over the 4px gutter — the
     same convention as the large-screen two-up, one column instead of two.
     The card's % basis is the track's content box, already inset by the
     24px padding-inline-start, so only one more margin is subtracted here.
     Smaller active pill for the narrow head. */
  .carousel { --card-w: calc(100% - var(--grid-margin)); }
  .carousel__dot--active { flex-basis: 72px; }
}

/* =============================================================
   Right-edge cursor capture — a → arrow chip that follows the
   pointer inside the right band; a click there advances. Driven
   entirely by carousel.js (position + the --cursor-active class);
   skipped on touch/coarse pointers.
   ============================================================= */
.carousel__cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 60;
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;                        /* centre on the pointer */
  display: grid;
  place-items: center;
  border-radius: var(--radius-round);
  background: var(--fg);
  color: var(--bg);
  pointer-events: none;                           /* never intercept the click */
  opacity: 0;
  transform: scale(0.6);
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.carousel__cursor svg { width: 24px; height: 24px; }
.carousel--cursor-active .carousel__cursor { opacity: 1; transform: scale(1); }
/* Hide the native cursor everywhere in the viewport while the arrow is
   active — the descendant rule also covers the card links, whose UA
   cursor:pointer would otherwise still show through. Only the → remains. */
.carousel--cursor-active .carousel__viewport,
.carousel--cursor-active .carousel__viewport * { cursor: none; }

@media (prefers-reduced-motion: reduce) {
  .carousel__dot { transition: background-color 0.35s ease; }
  .carousel__dot-fill { animation: none; }
}
