/* ════════════════════════════════════════════════════════════════════════
   css/components/carousel.css — THE CAROUSEL COMPONENT
   service.tnaado.ca · reusable · first used by /firm S.04

   ONE COMPONENT, NOT ONE SECTION. This is written to be dropped into any
   page: a horizontal, snapping, keyboard-driven strip of cards with real
   controls. css/reference.css's `.scroller` is nothing but `overflow-x:auto`
   with no snapping, no controls and no keyboard handling; this replaces it
   for anything that needs to read as a designed object.

   ── IT WORKS WITH NO JAVASCRIPT ─────────────────────────────────────────
   The viewport is a plain overflow-x container holding a plain <ul>. Every
   card is in the served HTML, in flow, in reading order, in the a11y tree —
   nothing is virtualised, nothing is lazy-mounted. With JS off you get a
   scrollable, snapping strip and no buttons; js/carousel.js adds `.is-live`,
   which is the only thing that reveals the control bar. Buttons that cannot
   work are never shown.

   ── MOTION ──────────────────────────────────────────────────────────────
   No autoplay. Ever. This is a portfolio a reader chooses to move through,
   not a hero banner, and an unattended carousel steals the reader's place.
   The only motion is the smooth scroll a button press asks for, and
   js/carousel.js drops that to `auto` under prefers-reduced-motion. Hover and
   focus affordances are under 250ms, matching css/firm-pages.css.

   ── COLOUR ──────────────────────────────────────────────────────────────
   Tokens only, from css/firm-pages.css: --ink/--plate/--plate-2/--bone/
   --ivory, --red-ink (small red on ink, 6.06:1) and --red-bone (small red on
   bone, 6.88:1), --dim-ink (9.49:1) and --dim-bone (6.36:1), and the two
   hairlines. No value in this file is invented. The bone/ivory variants at
   the foot exist so the component can move to a light band without anyone
   hand-picking a new grey.

   ── FIXED CHROME ────────────────────────────────────────────────────────
   Nothing here is positioned or sticky, so --chrome-bar-h is untouched and
   the 94px/86px bar keeps its own space. The viewport sets
   `overscroll-behavior-x: contain` only on the x axis, so a horizontal
   flick never captures the page's vertical scroll or the browser's
   back-swipe on the y axis.
   ════════════════════════════════════════════════════════════════════════ */

.carou {
  position: relative;
  margin: clamp(1.5rem, 3.5vw, 2.25rem) 0 0;
  /* ── THE STRIP MUST NEVER SIZE ITS OWN ANCESTORS ───────────────────────
     A scrolling box reports its MAX-CONTENT as its preferred width, so in any
     shrink-to-fit context this component asks to be as wide as every card laid
     end to end and its ancestors oblige. It is not hypothetical: 822px of
     overflow at a 390px viewport in this component's own harness, and the same
     defect cost /apps' example strip 543px inside a 390px viewport (fixed in
     css/scene.css).

     `min-width: 0` is the reflex and it is not enough. css/chrome.css sets
     `body { display: flex; flex-direction: column }` whenever `chrome-live` is
     absent — which is the whole no-JS state — and in a COLUMN flex container
     the horizontal axis is the CROSS axis, which `min-width` does not gate.
     An explicit width is what removes the ambiguity.

     css/components/proofcard.css already does this, but only under
     `.proof-rail`. It belongs on the component so a strip is safe wherever it
     is mounted rather than only where somebody remembered the wrapper. */
  min-width: 0;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
}

/* ── THE VIEWPORT ─────────────────────────────────────────────────────────
   tabindex="0" in the markup, because a scrollable region that keyboard
   users cannot reach is not scrollable. The focus ring is the sheet's. */
.carou__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  /* Same guard, one level down: this is the box that actually scrolls, so this
     is the box whose max-content leaks upward. See the note on `.carou`. */
  min-width: 0;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
}

.carou__track {
  display: flex;
  gap: clamp(1rem, 2.5vw, 1.75rem);
  list-style: none;
  margin: 0;
  /* The bottom padding is the scrollbar's lane; without it the bar sits on
     the card's own bottom edge on the platforms that draw one. */
  padding: 0 0 1rem;
}

.carou__item {
  flex: 0 0 clamp(15.5rem, 78vw, 23rem);
  scroll-snap-align: start;
  display: flex;
}

/* ── THE CONTROL BAR ──────────────────────────────────────────────────────
   Hidden until js/carousel.js sets .is-live. The arrows DISABLE at the ends
   rather than disappearing, so the row never changes width under the cursor
   and a keyboard user never loses the element he was on. */
.carou__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: .75rem;
}
.carou:not(.is-live) .carou__bar { display: none; }

.carou__count {
  margin: 0;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .08em;
  color: var(--dim-ink);
}
.carou__count b { color: var(--bone); font-weight: 500; }

.carou__nav { display: flex; gap: .5rem; }

.carou__btn {
  width: 2.75rem;
  height: 2.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-family: var(--mono);
  font-size: 1rem;
  line-height: 1;
  color: var(--bone);
  background: transparent;
  border: 1px solid var(--hair-ink);
  cursor: pointer;
}
.carou__btn[disabled] {
  color: var(--dim-ink);
  opacity: .45;
  cursor: default;
}
@media (hover: hover) {
  .carou__btn { transition: background-color .2s var(--ease), border-color .2s var(--ease); }
  .carou__btn:not([disabled]):hover {
    background: var(--plate-2);
    border-color: var(--red-ink);
  }
}


/* ══ THE PORTFOLIO CARD ═══════════════════════════════════════════════════
   The card the carousel was built for: a capture of the thing, then the
   name, then what class of work it was. The own-brand / client label is not
   decoration — it is the honest half of the section — so it sits ABOVE the
   name where it cannot be missed, and it is real text, not a colour. */
.pcard { display: flex; width: 100%; }

.pcard__link {
  display: flex;
  flex-direction: column;
  width: 100%;
  text-decoration: none;
  color: inherit;
  background: var(--plate);
  border: 1px solid var(--hair-ink);
}

/* The plate under the image is what a failed or slow capture shows: a dark
   rectangle the size of the card, never a broken-image glyph or a jump. */
.pcard__shot {
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--plate-2);
  border-bottom: 1px solid var(--hair-ink);
}
.pcard__shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

.pcard__body {
  display: block;
  padding: clamp(1rem, 2.2vw, 1.35rem);
}

.pcard__i {
  font-family: var(--mono);
  font-size: .7rem;
  color: var(--red-ink);
  margin-right: .6rem;
}

.pcard__kind {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .06em;
  color: var(--dim-ink);
}

.pcard__n {
  display: block;
  margin-top: .55rem;
  font-family: var(--serif);
  font-variation-settings: 'opsz' 48;
  font-size: clamp(1.15rem, 2.2vw, 1.4rem);
  line-height: 1.15;
}

.pcard__meta {
  display: block;
  margin-top: .35rem;
  font-size: .85rem;
  line-height: 1.45;
  color: var(--dim-ink);
}

.pcard__go {
  display: block;
  margin-top: .9rem;
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--red-ink);
}

@media (hover: hover) {
  .pcard__link { transition: border-color .2s var(--ease), background-color .2s var(--ease); }
  .pcard__link:hover { border-color: var(--red-ink); background: var(--plate-2); }
}


/* ══ LIGHT-GROUND VARIANTS ════════════════════════════════════════════════
   So the component can be dropped into a --bone or --ivory band without
   anybody inventing a second palette for it. */
.fp-band--bone .carou__count,
.fp-band--ivory .carou__count { color: var(--dim-bone); }
.fp-band--bone .carou__count b,
.fp-band--ivory .carou__count b { color: var(--ink); }

.fp-band--bone .carou__btn,
.fp-band--ivory .carou__btn { color: var(--ink); border-color: var(--hair-bone); }
.fp-band--bone .carou__btn[disabled],
.fp-band--ivory .carou__btn[disabled] { color: var(--dim-bone); }

.fp-band--bone .pcard__link,
.fp-band--ivory .pcard__link { background: var(--ivory); border-color: var(--hair-bone); }
.fp-band--bone .pcard__shot,
.fp-band--ivory .pcard__shot { background: var(--bone-2); border-bottom-color: var(--hair-bone); }
.fp-band--bone .pcard__i,  .fp-band--ivory .pcard__i,
.fp-band--bone .pcard__go, .fp-band--ivory .pcard__go { color: var(--red-bone); }
.fp-band--bone .pcard__kind, .fp-band--ivory .pcard__kind,
.fp-band--bone .pcard__meta, .fp-band--ivory .pcard__meta { color: var(--dim-bone); }

@media (hover: hover) {
  .fp-band--bone .carou__btn:not([disabled]):hover,
  .fp-band--ivory .carou__btn:not([disabled]):hover { background: var(--bone-2); border-color: var(--red-bone); }
  .fp-band--bone .pcard__link:hover,
  .fp-band--ivory .pcard__link:hover { background: var(--bone-2); border-color: var(--red-bone); }
}
