/* ==========================================================================
   Depth Carousel 1.0 — drag-scrolled card rail with optional depth
   --------------------------------------------------------------------------
   Plain CSS. No framework, no build step, no external request.

   The component is a horizontal scroll container. That choice is the whole
   accessibility story: before the script runs — and if it never runs — the
   rail already scrolls with a trackpad, a touch screen, a scrollbar and the
   arrow keys, because the browser does it. The script only adds pointer
   dragging, momentum, the endless loop and the depth transform on top.

   PROPORTIONS
   Every metric is expressed in em and derives from --dc-font-size, measured
   on the reference at a 20px body:

     body text     1                 (the reference)
     card         13.5 em wide, 21.55 em tall
     gutter        0.8 em
     pitch        14.3 em            (card + gutter — the loop unit)
     radius        0.4 em
     shadow        0 0.4em 1.2em
     card padding  1 em
     perspective 100 em

   So changing --dc-font-size rescales the whole component and nothing else
   needs to be touched.

   Order of this file:
     0. Typeface
     1. Design tokens
     2. Scene
     3. Rail (the scroll container)
     4. Track and slides
     5. Depth variant
     6. Card frame and media
     7. Text panels and their three colourways
     8. Edge veils
     9. Focus, reduced motion, forced colours, print

   Naming follows BEM: .depth-carousel, .depth-carousel__part,
   .depth-carousel--variant.
   ========================================================================== */

/* ==========================================================================
   0. Typeface
   --------------------------------------------------------------------------
   Shipped next to the stylesheet, so the component asks nothing of the
   network. It is a static face: the weight range below tells the browser to
   use the one drawing across 400–700 rather than manufacturing a bold, which
   on a pixel face smears the grid that is the whole point of it.

   The file is optional. Remove it and set --dc-font-pixel to any family: the
   component keeps its proportions, it only changes its voice.
   ========================================================================== */

@font-face {
  font-family: "Pixelify Sans";
  src: url("PixelifySans.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

/* ==========================================================================
   1. Design tokens
   ========================================================================== */

.depth-carousel {
  /* Scale. Every em below resolves against this one value. */
  --dc-font-size: 20px;

  /* Card geometry, measured on the reference (270 × 431 at a 20px body). The
     card is a fixed block there: it did not move at 1920, 1440, 1024, 768 or
     390 wide. Only the track padding follows the viewport. */
  --dc-card-width: 13.5em;
  --dc-card-height: 21.55em;
  --dc-gap: 0.8em;
  --dc-radius: 0.4em;
  --dc-card-padding: 1em;
  --dc-shadow: 0 0.4em 1.2em rgb(0 0 0 / 0.2);

  /* Scene. A band the height of a card plus a little air, which is what a
     component dropped into a page almost always wants. The reference fills the
     viewport instead: .depth-carousel--fullscreen puts that back. */
  --dc-scene-height: calc(var(--dc-card-height) + 6em);
  --dc-scene-bg: #f5f5f5;
  --dc-perspective: 100em;

  /* Card surfaces */
  --dc-card-bg: #fff;
  --dc-amber: rgb(255 140 0);
  --dc-amber-ink: #fff;
  --dc-amber-ink-soft: rgb(252 246 217);
  --dc-crimson: rgb(250 67 67);
  --dc-crimson-ink: #000;
  --dc-lime: rgb(186 235 73);
  --dc-lime-ink: #fff;

  /* Edge veils. The reference fades to a grey darker than its own background,
     so the edges dim rather than dissolve. Set --dc-veil-color to
     var(--dc-scene-bg) for a plain fade. */
  --dc-veil-size: 20%;
  --dc-veil-color: rgb(186 186 186);
  --dc-veil-opacity: 0.8;

  /* Motion. Every duration is here so it can be tuned without touching the
     script — the script reads --dc-friction and --dc-depth-span from the
     computed style. */
  --dc-hover-scale: 1.02;
  --dc-hover-duration: 0.3s;
  --dc-hover-ease: cubic-bezier(0.4, 0, 0.2, 1);
  --dc-friction: 0.94; /* velocity kept per 60 Hz frame after release */
  --dc-step-duration: 420ms; /* keyboard step, when smooth scrolling is on */
  /* Drift, in pixels per second. 60 is the measured value — one pixel per
     frame at 60 Hz. Negative goes the other way, 0 stops it. It pauses under
     the pointer, under the keyboard focus and in a hidden tab. */
  --dc-autoplay: 60;

  /* Depth. Zero by default: that is the reference, whose cards never leave
     the flat plane. .depth-carousel--depth switches the values on. */
  --dc-rotation: 0deg;
  --dc-depth: 0em;
  --dc-depth-dim: 0; /* how much the far cards are veiled, 0 to 1 */
  --dc-depth-span: 2.4; /* pitches over which the effect ramps to full */

  /* Type. Two families: one for the running copy, one for the pixel titles.
     Both are tokens, so a host page can hand the component its own. */
  --dc-font: "Instrument Sans", ui-sans-serif, system-ui, -apple-system,
    "Segoe UI", sans-serif;
  --dc-font-pixel: "Pixelify Sans", ui-monospace, monospace;

  /* Focus */
  --dc-focus-color: #111;
  --dc-focus-width: 2px;
  --dc-focus-offset: -4px;

  position: relative;
  display: block;
  height: var(--dc-scene-height);
  overflow: hidden;
  background-color: var(--dc-scene-bg);
  font-family: var(--dc-font);
  /* Set here, not on the parts: every em below then resolves against it. */
  font-size: var(--dc-font-size);
  /* Declared even when the cards are flat: it costs nothing and it is what
     makes --dc-rotation mean anything the moment it is set. */
  perspective: var(--dc-perspective);
  perspective-origin: center center;
}

/* Declared by the component rather than assumed from the page: without it, a
   host page with the default content-box adds padding and borders on top of
   every size below, and the cards come out wider than the pitch the loop is
   computed from. */
.depth-carousel,
.depth-carousel *,
.depth-carousel *::before,
.depth-carousel *::after {
  box-sizing: border-box;
}

/* ==========================================================================
   2. Scene
   ========================================================================== */

/* The reference's own scene: the full viewport. */
.depth-carousel--fullscreen {
  --dc-scene-height: 100vh;
}

/* Smaller cards, same drawing. Everything is em, so one value does it. */
.depth-carousel--compact {
  --dc-font-size: 14px;
}

/* ==========================================================================
   3. Rail — the scroll container
   ========================================================================== */

.depth-carousel__rail {
  height: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  /* The reference does not snap. Kept explicit so a host page's `scroll-snap`
     reset cannot leak in. */
  scroll-snap-type: none;
  overscroll-behavior-x: contain;
  /* Momentum is ours once enhanced; the browser's own is only in the way. */
  -webkit-overflow-scrolling: auto;
}

/* The scrollbar is the only affordance a visitor has before the script runs,
   so it is hidden only once dragging is available. */
.depth-carousel[data-dc-enhanced] .depth-carousel__rail {
  scrollbar-width: none;
  -ms-overflow-style: none;
  cursor: grab;
  /* Dragging a rail selects text on the way. Suppressed on the enhanced path
     only, so the no-script page keeps its selectable copy. */
  user-select: none;
  -webkit-user-select: none;
}

.depth-carousel[data-dc-enhanced] .depth-carousel__rail::-webkit-scrollbar {
  display: none;
}

.depth-carousel[data-dc-grabbing] .depth-carousel__rail {
  cursor: grabbing;
}

/* ==========================================================================
   4. Track and slides
   ========================================================================== */

.depth-carousel__track {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  height: 100%;
  gap: var(--dc-gap);
  /* Half a viewport minus half a card on each side: this is what lets the
     first and the last card sit exactly in the middle of the scene. */
  padding-inline: calc((100% - var(--dc-card-width)) / 2);
  margin: 0;
  list-style: none;
  transform-style: preserve-3d;
}

.depth-carousel__slide {
  flex: 0 0 auto;
  width: var(--dc-card-width);
  height: var(--dc-card-height);
  transform-origin: center center;
  transform-style: preserve-3d;
}

/* ==========================================================================
   5. Depth variant
   --------------------------------------------------------------------------
   The reference ships the whole machinery — perspective on the scene,
   preserve-3d on the track, transform-origin and will-change on every card —
   and then never writes a transform. This is that machinery, fed.

   The script sets two custom properties per slide:
     --dc-d    signed distance from the centre, in pitches, clamped to ±1
     --dc-ad   its absolute value
   Nothing else. The amounts stay here, in CSS, so they can be tuned without
   opening the script.
   ========================================================================== */

.depth-carousel--depth {
  --dc-rotation: 26deg;
  --dc-depth: 7em;
  --dc-depth-dim: 0.28;
  /* The reference's 2000px is a very long lens: at that distance a 270px card
     turned by 26° barely narrows, and the tilt reads as a rendering error
     rather than as depth. Halving the distance is what makes the same rotation
     legible. The flat default keeps the measured 100em. */
  --dc-perspective: 42em;
}

.depth-carousel--depth .depth-carousel__slide {
  transform: translateZ(calc(var(--dc-depth) * var(--dc-ad, 0) * -1))
    rotateY(calc(var(--dc-rotation) * var(--dc-d, 0) * -1));
  /* Promoted only where it is actually animated. Doing this on the flat
     default would hand 27 cards their own layer for nothing. */
  will-change: transform;
}

/* The far cards are veiled rather than faded: opacity on a card would let the
   scene's grey show through its own shadow, which reads as a smudge. */
.depth-carousel--depth .depth-carousel__card::after {
  position: absolute;
  inset: 0;
  z-index: 2;
  background-color: var(--dc-scene-bg);
  opacity: calc(var(--dc-depth-dim) * var(--dc-ad, 0));
  content: "";
  pointer-events: none;
}

/* ==========================================================================
   6. Card frame and media
   ========================================================================== */

.depth-carousel__card {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border: none;
  border-radius: var(--dc-radius);
  background-color: var(--dc-card-bg);
  box-shadow: var(--dc-shadow);
  transform-style: preserve-3d;
}

/* Image, video and text share one inner box: it is the box that scales on
   hover, inside the frame's overflow, so the corners stay clipped. */
.depth-carousel__media,
.depth-carousel__panel {
  width: 100%;
  height: 100%;
  transform: scale(1);
  transition: transform var(--dc-hover-duration) var(--dc-hover-ease);
}

.depth-carousel__media {
  display: block;
  object-fit: cover;
  /* An <img> is replaced content: without this it keeps its intrinsic ratio
     and leaves a white band under the card. */
  flex: 1;
  min-height: 0;
}

.depth-carousel__card:hover .depth-carousel__media,
.depth-carousel__card:hover .depth-carousel__panel {
  transform: scale(var(--dc-hover-scale));
}

/* A card that is a link or a button must not look like a form control. */
.depth-carousel__card:is(a, button) {
  padding: 0;
  color: inherit;
  font: inherit;
  text-align: inherit;
  text-decoration: none;
  cursor: inherit;
  appearance: none;
}

/* ==========================================================================
   7. Text panels
   ========================================================================== */

.depth-carousel__panel {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-between;
  padding: var(--dc-card-padding);
  background-color: var(--dc-panel-bg, var(--dc-card-bg));
  color: var(--dc-panel-ink, inherit);
}

.depth-carousel__title {
  width: 100%;
  margin: 0;
  font-size: var(--dc-title-size, 1.7em);
  font-weight: var(--dc-title-weight, 300);
  line-height: var(--dc-title-leading, 1.2);
  letter-spacing: -0.01em;
  /* A 3.95em word in a 13.5em card has nowhere to go: it must be allowed to
     break inside itself rather than overflow the frame. */
  overflow-wrap: break-word;
  word-break: break-word;
}

.depth-carousel__text {
  width: 100%;
  margin: 0;
  color: var(--dc-panel-ink-soft, var(--dc-panel-ink, inherit));
  font-size: var(--dc-text-size, 1em);
  font-weight: var(--dc-text-weight, 400);
  line-height: 1.3;
  letter-spacing: var(--dc-text-tracking, -0.01em);
  overflow-wrap: break-word;
}

/* The three colourways, measured one by one. Each carries its own type scale
   because the reference sets a different one on every card. */
.depth-carousel__panel--amber {
  --dc-panel-bg: var(--dc-amber);
  --dc-panel-ink: var(--dc-amber-ink);
  --dc-panel-ink-soft: var(--dc-amber-ink-soft);
  --dc-title-size: 1.7em;
  --dc-title-weight: 300;
  --dc-title-leading: 1.2;
  --dc-text-size: 1em;
  --dc-text-weight: 400;
  --dc-text-tracking: -0.01em;
}

.depth-carousel__panel--crimson {
  --dc-panel-bg: var(--dc-crimson);
  --dc-panel-ink: var(--dc-crimson-ink);
  --dc-panel-ink-soft: var(--dc-crimson-ink);
  --dc-title-size: 3.95em;
  --dc-title-weight: 700;
  --dc-title-leading: 1.2;
  --dc-text-size: 1em;
  --dc-text-weight: 500;
  --dc-text-tracking: -0.05em;
}

.depth-carousel__panel--lime {
  --dc-panel-bg: var(--dc-lime);
  --dc-panel-ink: var(--dc-lime-ink);
  --dc-panel-ink-soft: var(--dc-lime-ink);
  --dc-title-size: 2.3em;
  --dc-title-weight: 600;
  --dc-title-leading: 0.8;
  --dc-text-size: 1.1em;
  --dc-text-weight: 400;
  --dc-text-tracking: -0.05em;
}

/* Two of the three titles are set in the pixel face, the third in the running
   sans — which is what the reference does. The body copy never takes it: a
   pixel face at 1em is a decoration, not a paragraph. */
.depth-carousel__panel--crimson .depth-carousel__title,
.depth-carousel__panel--lime .depth-carousel__title {
  font-family: var(--dc-font-pixel);
}

/* ==========================================================================
   8. Edge veils
   --------------------------------------------------------------------------
   Drawn by the scene itself rather than by two elements in the markup. They
   carry no content and no behaviour, so asking an author to write — and
   remember — two decorative divs would be charging them for nothing. As
   pseudo-elements they also survive a page without JavaScript, which an
   injected element would not.

   To remove them: --dc-veil-opacity: 0.
   ========================================================================== */

.depth-carousel::before,
.depth-carousel::after {
  position: absolute;
  top: 0;
  z-index: 10;
  width: var(--dc-veil-size);
  height: 100%;
  opacity: var(--dc-veil-opacity);
  content: "";
  pointer-events: none;
}

.depth-carousel::before {
  left: 0;
  background-image: linear-gradient(to right, var(--dc-veil-color), transparent);
}

.depth-carousel::after {
  right: 0;
  background-image: linear-gradient(to left, var(--dc-veil-color), transparent);
}

/* ==========================================================================
   9. Focus, reduced motion, forced colours, print
   ========================================================================== */

/* The rail carries the keyboard focus, so it must show it. The offset is
   negative because the scene clips: an outline drawn outside would be cut. */
.depth-carousel__rail:focus-visible {
  outline: var(--dc-focus-width) solid var(--dc-focus-color);
  outline-offset: var(--dc-focus-offset);
}

/* A card that is itself focusable keeps its own ring, above the veils. */
.depth-carousel__card:focus-visible {
  outline: var(--dc-focus-width) solid var(--dc-focus-color);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .depth-carousel {
    /* A long glide is motion spread over time, exactly what was asked to
       stop: the momentum is cut, not slowed. And a rail that drifts on its
       own is the one thing this preference is most clearly about. */
    --dc-friction: 0;
    --dc-step-duration: 0ms;
    --dc-hover-duration: 0ms;
    --dc-hover-scale: 1;
    --dc-autoplay: 0;
  }

  .depth-carousel--depth {
    /* The depth stays — it is a static property of where a card sits, not an
       animation — but it is halved so the rail reads calmer in motion. */
    --dc-rotation: 13deg;
    --dc-depth: 3.5em;
  }

  .depth-carousel__media,
  .depth-carousel__panel {
    transition: none;
  }
}

@media (forced-colors: active) {
  .depth-carousel {
    --dc-shadow: none;
  }

  /* Backgrounds are dropped in forced colours, so a gradient veil turns into
     an opaque grey plate over the cards. */
  .depth-carousel::before,
  .depth-carousel::after {
    display: none;
  }

  .depth-carousel__card {
    border: 1px solid CanvasText;
  }

  .depth-carousel__rail:focus-visible,
  .depth-carousel__card:focus-visible {
    outline-color: Highlight;
  }

  /* Turning the cards away from the reader is a transform, which forced
     colours cannot flatten. Better to stand them up. */
  .depth-carousel--depth {
    --dc-rotation: 0deg;
    --dc-depth: 0em;
    --dc-depth-dim: 0;
  }
}

@media print {
  .depth-carousel {
    height: auto;
    overflow: visible;
  }

  .depth-carousel__rail {
    height: auto;
    overflow: visible;
  }

  .depth-carousel__track {
    flex-wrap: wrap;
    padding-inline: 0;
  }

  .depth-carousel::before,
  .depth-carousel::after {
    display: none;
  }

  /* Clones carry no information a sheet of paper needs. */
  .depth-carousel__slide[data-dc-clone] {
    display: none;
  }
}
