/*
 * Shared shell for the five category pages (数字展厅 / 广告活动 / 商业视频 /
 * AIGC影视 / 空间娱乐).
 *
 * All five mockups are the same page: Header(80) → Hero(1000, full-bleed art with a
 * centred four-line title group) → a dark or light body → Footer(600). The body is
 * a fixed-ratio .stage so every media frame and caption keeps the exact mockup
 * coordinate; below 900px the stage reflows and the blocks stack (see the tail of
 * this file).
 *
 * Geometry from design/ANALYSIS.md §3.
 */

/* ---------------------------------------------------------------- hero ---- */

.cat-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--c-black);
  color: var(--c-white);
}

.cat-hero__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  scale: 1.018;
  transition:
    opacity 1.15s ease,
    scale 1.8s var(--ease-out);
}

.cat-hero__bg picture {
  display: block;
  width: 100%;
  height: 100%;
}

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

/*
 * The group spans the full stage and each line centres itself, rather than the
 * group shrink-wrapping its widest line: otherwise all four lines inherit their
 * centring from whichever line happens to be longest, and editing the English
 * subtitle would move the Chinese title.
 */
.cat-hero__titles {
  z-index: 1;
  opacity: 0;
  translate: 0 18px;
  text-align: center;
  transition:
    opacity 1s ease 720ms,
    translate 1s var(--ease-out) 720ms;
}

.is-ready .cat-hero__bg,
.is-ready .cat-hero__titles {
  opacity: 1;
  scale: 1;
  translate: 0 0;
}

@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) .cat-hero__bg,
  html:not(.force-motion) .cat-hero__titles {
    opacity: 1;
    scale: 1;
    translate: none;
    transition: none;
  }
}

/*
 * Sizes and tracking were solved jointly across two pages (tools/typefit.py): one
 * string leaves size and tracking interchangeable, but the same style over two
 * different strings pins both. The Chinese subtitle lands on 20px/400 with 14.000px of
 * tracking, agreeing to 0.007px between 空间娱乐 and 商业视频.
 *
 * Weights come from design-vs-build ink coverage in the same window
 * (tools/inkcmp.py), read on 商业视频 whose hero art is dark behind the titles —
 * 空间娱乐's glow makes an absolute reading off the mockup meaningless.
 *
 * Margins are ink-top to ink-top deltas converted through the font's OS/2 metrics
 * (tools/inkoffset.py), so `line-height: 1` throughout keeps the arithmetic honest.
 */
.cat-hero__cn,
.cat-hero__en,
.cat-hero__sub-cn,
.cat-hero__sub-en {
  line-height: 1;
  letter-spacing: calc(var(--track, 0) * var(--u));
  /*
   * Tracking also pads *after* the last glyph, so a centred line's ink sits half a gap
   * left of centre. An indent moves a centred line by only half its value (it shrinks
   * the space the line is then centred in), so the indent is the full tracking to buy
   * half a gap of travel. Exact for any string, unlike the per-page nudge this
   * replaces: both pages read a matching -4px before this, and 0 after.
   */
  text-indent: calc(var(--track, 0) * var(--u));
}

.cat-hero__cn {
  --track: 0.917;
  margin: 0;
  font-size: calc(64 * var(--u));
  font-weight: var(--fw-medium);
}

.cat-hero__en {
  margin: calc(17.9 * var(--u)) 0 0;
  font-size: calc(32 * var(--u));
  font-weight: var(--fw-bold);
}

/*
 * The two subtitle lines are set to the same width on every page. Usually the Chinese
 * line takes the standard 14px of tracking and the English is stretched to match it;
 * on 广告活动 the Chinese is only five glyphs, so it is the stretched one instead. Both
 * tracking values therefore come from the page data.
 */
.cat-hero__sub-cn {
  --track: var(--sub-cn-track, 14);
  margin: calc(69.4 * var(--u)) 0 0;
  font-size: calc(20 * var(--u));
  font-weight: var(--fw-regular);
}

.cat-hero__sub-en {
  --track: var(--sub-en-track, 2);
  margin: calc(14.8 * var(--u)) 0 0;
  font-size: calc(13 * var(--u));
  font-weight: var(--fw-regular);
}

/* ---------------------------------------------------------------- body ---- */

/*
 * The body is a flow stage: blocks stack in document order and the mockup's rhythm is
 * reproduced with three numbers per page (--first-top / --block-gap / --tail-gap)
 * instead of a coordinate per block.
 *
 * The mockup's gaps are measured between *ink* edges, but CSS spaces line boxes, so
 * the two constants below convert, both for the 32px/700 caption: --ink-lead is how
 * far the ink top sits below the box top, --ink-tail how far the box bottom sits below
 * the ink bottom. The latter is negative because at line-height 1 the glyphs overflow
 * their box slightly. Derived from the font's OS/2 metrics — the pair Chromium actually
 * lays out with, not hhea — and confirmed against a screenshot.
 */
.cat-body {
  position: relative;
  /*
   * `tail = box height - lead - ink height`, with the ink height read off the mockup
   * so both ends of every gap are measured the same way. Reading it off a render
   * instead gives a smaller number (antialiased edges the mockup's threshold keeps)
   * and the rhythm drifts ~1.5px per block.
   */
  --ink-lead: 1.5; /* 32px/700 caption, ink 31 */
  --ink-tail: -0.5;
  --sub-lead: 0.84; /* 18px/400 location line, ink 18 */
  --sub-tail: -0.84;

  padding-top: calc(var(--first-top) * var(--u));
  padding-bottom: calc((var(--tail-gap) - var(--ink-tail)) * var(--u));
}

.cat-body--has-sub {
  padding-bottom: calc((var(--tail-gap) - var(--sub-tail)) * var(--u));
}

.cat-body--dark {
  background: var(--c-black);
  color: var(--c-white);
}

.cat-body--light {
  background: var(--c-white);
  color: var(--c-black);
}

.cat-block {
  width: calc(var(--mw) * var(--u));
  margin-inline: auto;
  /* 数字展厅's frames and captions all sit 3px right of centre in the mockup */
  translate: calc(var(--content-dx, 0) * var(--u));
}

/*
 * The gap below a block is measured from the ink of whatever its last line is, so the
 * correction depends on which line that is: the 32px title on the video pages, the
 * 18px location line on the carousel pages.
 */
.cat-block + .cat-block {
  margin-top: calc((var(--block-gap) - var(--ink-tail)) * var(--u));
}

.cat-body--has-sub .cat-block + .cat-block {
  margin-top: calc((var(--block-gap) - var(--sub-tail)) * var(--u));
}

/* AIGC影视's spacing is not uniform; blocks that follow an exception carry the value. */
.cat-block[style*='--gap-before'] {
  margin-top: calc(var(--gap-before) * var(--u));
}

/*
 * A media frame at its mockup coordinate. Most clips are a clean 16:9, but some
 * exports are not (沉浸式空间 is 2552×1438) and the mockup places those at their own
 * 1x size rather than stretching them, so the ratio is per-block.
 */
/*
 * --mdx nudges the frame off the centred column without moving the caption, for the
 * blocks the mockup did not place at the exact centre (沉浸式空间 sits at x 324 where
 * centring its 1276px frame gives 322). Two pixels is invisible on a flat colour and
 * very visible in a diff of detailed footage.
 */
.cat-media {
  width: 100%;
  overflow: hidden;
  translate: calc(var(--mdx, 0) * var(--u));
  /* Only ever seen while a clip decodes: object-fit fills the frame afterwards. */
  background: #111;
}

/*
 * Except on the dark pages, where the VR frame masks its video down to the
 * goggle lens and leaves the ground on show for good — #111 drew a visibly
 * lighter rectangle across the section's black.
 */
.cat-body--dark .cat-media {
  background: var(--c-black);
}

.cat-media video,
.cat-media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: var(--ar, 16 / 9);
  object-fit: cover;
}

.cat-media--reel {
  position: relative;
  cursor: default;
}

.cat-reel__play {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 2;
  width: clamp(112px, 8vw, 156px);
  aspect-ratio: 1.45;
  border: 0;
  border-radius: 999px;
  background: #fff;
  overflow: hidden;
  isolation: isolate;
  opacity: 0;
  scale: 0.82;
  translate: -50% -50%;
  transition:
    opacity 220ms ease,
    scale 380ms cubic-bezier(0.22, 1, 0.36, 1);
}

.cat-reel__play::after {
  content: "";
  position: absolute;
  inset: -35%;
  z-index: -1;
  border-radius: 50%;
  background: #ef3711;
  scale: 0;
  transition: scale 560ms cubic-bezier(0.22, 1, 0.36, 1);
}

.cat-reel__play::before {
  content: "";
  position: absolute;
  left: 53%;
  top: 50%;
  z-index: 1;
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;
  border-left: 16px solid #000;
  translate: -50% -50%;
  transition: border-left-color 260ms ease;
}

.cat-media--reel:hover .cat-reel__play,
.cat-reel__play:focus-visible {
  opacity: 1;
  scale: 1;
}

.cat-reel__play:hover,
.cat-reel__play:focus-visible {
  background: #fff;
}

.cat-reel__play:hover::after,
.cat-reel__play:focus-visible::after {
  scale: 1;
}

.cat-reel__play:hover::before,
.cat-reel__play:focus-visible::before {
  border-left-color: #fff;
}

.cat-reel__cursor {
  position: fixed;
  left: 50%;
  top: 50%;
  z-index: 4;
  display: grid;
  width: 76px;
  height: 76px;
  place-items: center;
  border-radius: 50%;
  background: rgb(255 255 255 / 92%);
  box-shadow: 0 10px 36px rgb(0 0 0 / 24%);
  color: #111;
  font-size: 42px;
  font-weight: 300;
  line-height: 1;
  opacity: 0;
  translate: -50% -50%;
  pointer-events: none;
}

.cat-media--reel:fullscreen,
.cat-media--reel.is-reel-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 2147483646;
  width: 100vw;
  max-width: none;
  height: 100vh;
  translate: none;
  background: #000;
  cursor: none;
}

html[data-video-fullscreen="true"],
html[data-video-fullscreen="true"] body {
  overflow: hidden;
}

.cat-media--reel:fullscreen video,
.cat-media--reel.is-reel-fullscreen video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.cat-media--reel:fullscreen .cat-reel__play,
.cat-media--reel.is-reel-fullscreen .cat-reel__play {
  display: none;
}

.cat-media--reel:fullscreen .cat-reel__cursor,
.cat-media--reel.is-reel-fullscreen .cat-reel__cursor {
  opacity: 1;
}

/*
 * The goggle silhouette is lifted straight off the mockup (tools/vrmask.py) rather
 * than approximated with clip-path: the curve where the two lenses meet the nose
 * bridge is not describable in arcs without visibly missing the design.
 */
.cat-media--vr {
  position: relative;
  overflow: visible;
}

.cat-media--vr > video {
  mask-image: url('/assets/images/vr-screen-mask.png');
  mask-size: 100% 100%;
  mask-repeat: no-repeat;
  -webkit-mask-image: url('/assets/images/vr-screen-mask.png');
  -webkit-mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
}

.cat-media__vr-shell {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  pointer-events: none;
}

.cat-media__vr-shell img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.cat-media--vr + .cat-caption {
  position: relative;
  z-index: 2;
  translate: 0 calc(-123 * var(--u));
}

.cat-block[data-scroll-scale] {
  scale: var(--scroll-scale, 0.68);
  transform-origin: 50% 50%;
}

@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) .cat-block[data-scroll-scale] {
    scale: 1;
  }
}

.cat-caption {
  margin: calc((var(--caption-gap, 34) - var(--ink-lead)) * var(--u)) 0 0;
  font-size: calc(32 * var(--u));
  font-weight: var(--fw-bold);
  line-height: 1;
  white-space: nowrap;
}

.cat-body--caption-start .cat-caption {
  text-align: start;
}

.cat-body--caption-center .cat-caption {
  text-align: center;
}

/*
 * The mockup leaves 28.5px of *ink* between the project name and its floor area; the
 * glyphs either side contribute about 3px of side bearing, so the margin is 25.5. The
 * two spans are emitted without whitespace between them, so nothing else adds width.
 */
.cat-caption__area {
  margin-left: calc(25.5 * var(--u));
}

.cat-caption__area sup {
  font-size: 0.6em;
  vertical-align: super;
  line-height: 0;
}

/*
 * The country/city pair is split by a wide space in the mockup (about 10.5px of
 * advance). HarmonyOS Sans sets a space at only 0.27em — 4.9px here — so the rest
 * comes from word-spacing; the one location with no space is unaffected.
 */
.cat-caption-sub {
  margin: calc((var(--sub-gap) - var(--ink-tail) - var(--sub-lead)) * var(--u)) 0 0;
  font-size: calc(18 * var(--u));
  font-weight: var(--fw-regular);
  line-height: 1;
  word-spacing: calc(5.6 * var(--u));
  white-space: nowrap;
}

/* Effect 01 — stagger rise (text-effects.html), 广告活动项目名 / 面积 / 地点 */
.fx-rise {
  overflow: hidden;
}

.fx-rise__ch {
  display: inline-block;
  transform: translateY(115%) rotate(6deg);
  transition: transform 1s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}

.fx-rise.in .fx-rise__ch {
  transform: translateY(0) rotate(0deg);
}

@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) .fx-rise__ch {
    transform: none;
    transition: none;
  }
}

/* Effect 02 — line mask reveal (text-effects.html) */
.fx-lines__line {
  display: block;
  overflow: hidden;
}

.fx-lines__inner {
  display: block;
  opacity: 0;
  transform: translate3d(0, 105%, 0);
  backface-visibility: hidden;
  transition:
    transform 0.78s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.38s ease;
  will-change: transform, opacity;
}

.fx-lines.in .fx-lines__inner {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

body[data-page="digital"] .fx-lines__inner {
  transition-duration: 0.78s, 0.38s;
}

@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) .fx-lines__inner {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Effect 04 — word / glyph blur-in (text-effects.html), AIGC 标题与正文 */
.fx-blur__w {
  display: inline-block;
  filter: blur(14px);
  opacity: 0;
  transform: translateY(14px);
  transition:
    filter 0.9s ease,
    opacity 0.9s ease,
    transform 0.9s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: filter, transform, opacity;
}

.fx-blur.in .fx-blur__w {
  filter: blur(0);
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) .fx-blur__w {
    filter: none;
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* --------------------------------------------------------------- prose ---- */

/*
 * AIGC影视 pairs each clip with a paragraph. The body is 16px on a 24px line — 45
 * characters fill the 726px column exactly — and the block's last line, not its box,
 * is what the measured gaps below are counted from.
 */
.cat-body--prose {
  --body-lead: 4.75; /* ink top inside a 16/24 line box */
  --body-tail: 3.25;
}

.cat-prose__title {
  margin: 0;
  font-size: calc(32 * var(--u));
  font-weight: var(--fw-bold);
  line-height: 1;
}

.cat-prose__body {
  margin: 0;
  font-size: calc(16 * var(--u));
  font-weight: var(--fw-regular);
  line-break: strict;
  line-height: calc(24 * var(--u));
  overflow-wrap: break-word;
  word-break: normal;
}

.cat-block--landscape .cat-prose {
  display: flex;
  margin-top: calc((var(--caption-gap) - var(--ink-lead)) * var(--u));
}

.cat-block--landscape .cat-prose__title {
  flex: 0 0 calc(var(--tw, 554) * var(--u));
}

/*
 * The paragraph column runs a few px past the frame's right edge. Its width is fixed by
 * where the mockup breaks its lines: 727.4 is the longest line (45 characters plus " AI ")
 * and 736 would let a 46th character up, so anything between the two reproduces the
 * mockup exactly.
 */
.cat-block--landscape .cat-prose__body {
  flex: 0 0 calc(var(--bw, 730) * var(--u));
  /* Box-aligning the two columns puts the paragraph a shade low against the mockup.
     Shifted rather than margined so the row keeps its full height. */
  translate: 0 calc(-1.75 * var(--u));
}

/*
 * Portrait: text left, frame right, in a 1040 row. The mockup rests the paragraph's last
 * line of ink on the frame's bottom edge, so the prose hangs its own tail below the row.
 */
.cat-block--portrait {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
}

.cat-block--portrait .cat-prose {
  width: calc(var(--tw, 400) * var(--u));
  margin-bottom: calc(var(--body-tail) * -1 * var(--u));
}

.cat-block--portrait .cat-prose__body {
  margin-top: calc((var(--title-gap) - var(--ink-tail) - var(--body-lead)) * var(--u));
}

.cat-block--portrait .cat-media {
  /*
   * Word mockup keeps the desktop frame at the full 520×924 design size even
   * when it is taller than the viewport; shrinking it to one viewport made the
   * text/image composition noticeably too narrow at 1920×900.
   */
  width: min(520px, calc(520 * var(--u)));
}

/* ------------------------------------------------------------ carousel ---- */

/*
 * 数字展厅 / 广告活动. The 1280 viewport shows a 1120 frame plus 128px of the next
 * project; the 32px gutter between them means the track steps 1152 per slide, so the
 * peek is just the next slide showing through rather than a separate element.
 *
 * The pixel offset is written by js/slider.js as transform: translate3d(...).
 * Transitioning transform (not --i) is what actually slides the pictures.
 */
.cat-carousel {
  position: relative;
  overflow: hidden;
  touch-action: pan-y;
}

.cat-carousel.is-swiping {
  touch-action: none;
  user-select: none;
}

.cat-carousel__track {
  display: flex;
  gap: calc(32 * var(--u));
  margin: 0;
  padding: 0;
  list-style: none;
  transform: translate3d(0, 0, 0);
  transition: transform 680ms var(--ease-out);
}

.cat-carousel__track.is-jumping,
.cat-carousel__track.is-dragging {
  transition: none;
}

.cat-carousel__slide {
  flex: 0 0 calc(1120 * var(--u));
}

.cat-carousel__slide img,
.cat-carousel__empty {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1120 / 520;
  object-fit: cover;
  -webkit-user-drag: none;
  user-select: none;
}

.cat-carousel__empty {
  background: #ececec;
}

/* Sits on the peek column, at the position measured off the mockup.
   全局备注: 42×42, 间距 8; 默认黑箭 + 白 70%; hover 白箭 + 黑 50%; 移入才显示. */
.cat-carousel__nav {
  position: absolute;
  top: calc(229 * var(--u));
  left: calc(1164 * var(--u));
  display: flex;
  gap: calc(8 * var(--u));
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms var(--ease-out);
}

.cat-carousel:hover .cat-carousel__nav,
.cat-carousel.is-hover .cat-carousel__nav,
.cat-carousel:focus-within .cat-carousel__nav {
  opacity: 1;
  pointer-events: auto;
}

.cat-carousel__btn {
  display: grid;
  place-items: center;
  width: calc(42 * var(--u));
  height: calc(42 * var(--u));
  padding: 0;
  border: 0;
  background: rgb(255 255 255 / 0.7);
  cursor: pointer;
  transition: background 200ms var(--ease-out), filter 200ms var(--ease-out);
}

.cat-carousel__btn:hover,
.cat-carousel__btn:focus-visible {
  background: rgb(0 0 0 / 0.5);
}

.cat-carousel__btn img {
  width: calc(30 * var(--u));
  height: auto;
  transition: filter 200ms var(--ease-out);
}

.cat-carousel__btn:hover img,
.cat-carousel__btn:focus-visible img {
  filter: invert(1);
}

@media (hover: none) {
  .cat-carousel__nav {
    opacity: 1;
    pointer-events: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) .cat-carousel__track {
    transition: none;
  }

  html:not(.force-motion) .cat-carousel__nav {
    transition: none;
  }
}

/* --------------------------------------------------------- responsive ---- */

/*
 * Below 900px the coordinate grid stops being useful: a 32px caption scaled by
 * viewport width lands at 11px on a phone. The stage reflows and the blocks become
 * a simple stack with legible type.
 */
@media (max-width: 900px) {
  /*
   * The hero's ratio box is gone, so the artwork needs a height to fill and the
   * title group has to be centred in it rather than pinned to a coordinate.
   */
  .cat-hero {
    display: grid;
    place-items: center;
    min-height: min(72vh, 620px);
    padding: calc(var(--header-height) + 48px) var(--page-gutter) 64px;
  }

  .cat-hero__cn {
    font-size: var(--fs-banner);
    line-height: 1.1;
  }

  .cat-hero__en {
    margin-top: 10px;
    font-size: var(--fs-banner-sub);
    line-height: 1.2;
  }

  /* The design's tracking is a proportion of a 20px line; keep it relative here. */
  .cat-hero__sub-cn {
    --track: 0;
    margin-top: 26px;
    font-size: var(--fs-body);
    line-height: 1.7;
    letter-spacing: 0.08em;
    white-space: normal;
  }

  .cat-hero__sub-en {
    --track: 0;
    margin-top: 8px;
    font-size: var(--fs-note);
    line-height: 1.6;
    letter-spacing: 0.03em;
    white-space: normal;
  }

  /* Specific enough to beat the desktop `--has-sub` variants above. */
  .cat-body.stage--reflow {
    padding-block: 64px 72px;
  }

  .cat-block {
    width: min(100% - var(--page-gutter) * 2, 720px);
  }

  .cat-block[data-scroll-scale] {
    scale: 1;
  }

  .cat-body.stage--reflow .cat-block + .cat-block {
    margin-top: 56px;
  }

  .cat-media {
    /* the sub-pixel nudge is meaningless once the column is fluid */
    translate: none;
  }

  .cat-caption {
    margin-top: 14px;
    font-size: var(--fs-h3);
    white-space: normal;
  }

  /*
   * Neither prose layout survives a phone column: the two text columns stack, and the
   * portrait frame moves above its paragraph so the picture still leads the block.
   */
  .cat-block--landscape .cat-prose {
    display: block;
  }

  .cat-block--portrait {
    flex-direction: column;
    align-items: stretch;
  }

  .cat-block--portrait .cat-prose {
    order: 2;
  }

  .cat-block--landscape .cat-prose__title,
  .cat-block--portrait .cat-prose,
  .cat-block--landscape .cat-prose__body {
    width: auto;
    margin: 0;
    translate: none;
  }

  .cat-block--portrait .cat-media {
    width: min(100%, calc((520 / 924) * (100svh - var(--header-height) - 48px)));
    margin-inline: auto;
    translate: none;
  }

  .cat-block--landscape .cat-prose {
    margin-top: 14px;
  }

  .cat-prose__title {
    font-size: var(--fs-h3);
    line-height: 1.3;
  }

  .cat-block--portrait .cat-prose__title {
    margin-top: 14px;
  }

  .cat-prose__body,
  .cat-block--portrait .cat-prose__body {
    margin-top: 10px;
    font-size: var(--fs-note);
    line-height: 1.8;
  }

  .cat-caption__area {
    margin-left: 0.6em;
  }

  .cat-caption-sub {
    margin-top: 6px;
    font-size: var(--fs-note);
    word-spacing: 0.2em;
  }

  /*
   * No room for the peek column on a phone: one slide fills the width. JS still
   * measures slide width + gap and writes the same translate3d offset.
   */
  .cat-carousel__track {
    gap: 16px;
  }

  .cat-carousel__slide {
    flex-basis: 100%;
  }

  .cat-carousel__nav {
    inset: auto 12px 12px auto;
    opacity: 1;
    pointer-events: auto;
  }

  /* Phones show the buttons over the photo itself, so they need more contrast. */
  .cat-carousel__btn {
    width: 40px;
    height: 40px;
    background: rgb(255 255 255 / 0.88);
  }

  .cat-carousel__btn img {
    width: 22px;
  }
}

@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  html:not(.force-motion) .cat-carousel__track {
    transition: none;
  }
}
