/* ============================================================================
   Inspiration showcase — hero banner + ticker, the playable grid, and the
   ambient carousel that runs behind the wizard.

   Loaded alongside style.css, whose custom properties (--accent-1, --card,
   --radius-lg, ...) this file reuses rather than restating.

   Two page MODES, toggled on .app by web/static/js/app.js:
     .is-home — hero + wizard card + grid, page scrolls, top-aligned
     .is-flow — wizard card alone, vertically centred, ambient carousel behind
   ========================================================================== */

/* ── page modes ─────────────────────────────────────────────────────────────
   The shell centres a single card by default (style.css). Home mode is a full
   page instead: wider, top-aligned, scrollable. */
/* :has() covers the pre-JS first paint; app.js also stamps body.mode-home, so the
   layout is correct in a browser without :has() as soon as scripts run. */
.shell:has(.app.is-home),
body.mode-home .shell { justify-content: flex-start; padding: 14px 20px 48px; }

.app.is-home { max-width: 1180px; gap: 18px; }
/* Home mode is a LANDING page: everything above the grid is competing with it for the
   first screen, so the padding is tighter here than style.css's (the flow keeps the
   roomier version, having nothing below it to reach). Together with the two-column step
   1, that keeps the first tile row near the fold on a 900px laptop.
   The WIDTH is full, matching the banner above and the grid below. It was capped at 780px,
   which left ~200px of empty gutter down each side of the card while the two sections
   bracketing it ran the full 1180 -- the card read as a floating box on a page that was
   otherwise a clean stack. Step 1 is already a two-column intake, so the extra width
   goes into the two panels rather than stretching a single input; only step 1 is
   affected, because every later step switches .app to is-flow (680px) instead. */
.app.is-home .card--app { max-width: 100%; align-self: stretch; padding: 26px 32px 28px; }
/* ...but not the primary button. Stretched across 1100px it stops reading as a button
   at all; the title and subtitle are already centred, so a centred, capped button is
   what the rest of the card is doing anyway.
   display:flex is what makes the auto margins centre it -- .btn is inline-flex, and
   auto margins do nothing on an inline-level box, so capping the width alone just
   parked it against the left edge. */
.app.is-home #state-link .btn--block {
  display: flex;
  max-width: 420px;
  margin-left: auto;
  margin-right: auto;
}
.app.is-home .card__title { font-size: 1.42rem; }
.app.is-home .card__subtitle { margin-bottom: 20px; }

/* Wide screens: the hero is a THIN full-width banner ACROSS THE TOP, with the wizard
   card below it. It used to be a tall left column beside the card (a two-column grid),
   which was how the Inspiration grid was kept near the fold -- but it made the hero as
   tall as the card next to it (~380px) purely because the grid row stretched it, so the
   page led with a huge purple slab.

   Laid out as a band instead, the whole hero costs ~150px and the card keeps its own
   width below, which is both closer to the reference and still cheap enough vertically:
   band + card + gaps land the Inspiration heading at roughly the same place the old
   two-column layout did on a 900px laptop.

   The copy sits left; the clip ticker fills the rest of the band and BLEEDS to its top,
   bottom and right edges -- hence the vertical padding living on .hero__copy rather than
   on .hero itself, so the strip has full-height room to fill. The banner's own
   `overflow:hidden` + border-radius does the clipping at the right corner.

   861px, not 1080px: that is where the ≤860 block below stacks the hero into a column,
   and the two must not overlap or the bleeding strip would land in a column layout.
   (inspiration.js drops the ticker entirely at ≤720px, so between 721 and 860 the strip
   still exists in the DOM and is laid out by that stacked block.) */
@media (min-width: 861px) {
  .app.is-home .hero {
    align-items: stretch;
    min-height: 156px;
    padding: 0 0 0 30px;
    gap: 24px;
  }
  /* A FIXED 520px, not a percentage. The band's height is set by however many lines
     this column wraps to, so a percentage made it breathe: at ~600px the title took 3
     lines and the sub 3, and the "thin" banner came out 250px tall. 520px is the width
     at which the title fits 2 lines and the 135-character sub fits 2 -- which is what
     holds the whole band near 156px. Shrinking this column does not make the banner
     smaller; it makes it TALLER. */
  .app.is-home .hero__copy {
    flex: 0 0 520px;
    max-width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 16px 0;
  }
  /* No ticker to sit beside: the template omits it when there are no strip clips, and
     inspiration.js removes it outright when autoplay is blocked. Without this the copy
     would hold its 52% and leave half a banner of empty purple. */
  .app.is-home .hero__copy:only-child { flex: 1 1 auto; }

  /* Sized to WRAP, not to look big: the title has to land on 2 lines and the sub on 2
     inside the 520px column above, because every extra line is ~20px of banner height.
     max-width:none on the sub undoes the base 56ch cap, which at this size would have
     held it to ~330px and spilled it onto four lines. */
  .app.is-home .hero__title { font-size: clamp(1.2rem, 1.6vw, 1.4rem); line-height: 1.06; }
  .app.is-home .hero__sub {
    max-width: none;
    font-size: .8rem;
    margin-bottom: 10px;
  }
  .app.is-home .btn--hero { align-self: flex-start; padding: 8px 16px; font-size: .85rem; }

  .app.is-home .hero__ticker {
    flex: 1 1 auto;
    min-width: 0;
    max-width: none;
    align-self: stretch;
    /* Feathered on the LEFT only. The right end must read as running off the edge of
       the banner (as in the reference); a right-hand fade would announce that the
       strip stops there, and the rounded corner already cuts it cleanly. */
    -webkit-mask-image: linear-gradient(to right, transparent, #000 9%);
    mask-image: linear-gradient(to right, transparent, #000 9%);
  }
  /* Inset from the band's top and bottom, NOT bled to its edges. Full-bleed tiles with
     square corners read as one smeared filmstrip; the reference keeps them as separate
     rounded cards floating in the purple, which is what makes the strip legible at this
     size. The padding is what produces the inset, so the row must still be 100% tall. */
  .app.is-home .hero__ticker { padding: 13px 0; }
  .app.is-home .hero__ticker .ticker__row { height: 100%; align-items: stretch; }
  /* Height comes from the band; 72px width keeps that at ~9:16 so object-fit crops
     nothing visible. 72 and the 8px gap above are ALSO hard-coded in inspiration.js's
     mountTicker() (opts.width / opts.gap), which uses them to hold the marquee at a
     constant scroll speed regardless of clip count -- change them here and you must
     change them there, or the strip silently scrolls at the wrong speed. */
  .app.is-home .hero__ticker .ticker__item {
    width: 72px;
    height: 100%;
    aspect-ratio: auto;
    border-radius: 9px;
  }
}

/* The wizard card stays wide once the visitor is IN the flow too -- the language
   step now carries a video preview beside its controls and needs the room. The
   max-width has to be lifted on .app as well, since style.css caps that at 480px
   and the card can never exceed its own container. */
.app.is-flow { max-width: 680px; }
.app.is-flow .card--app { max-width: 680px; }

/* Hero and grid exist only in home mode; the ambient strip only in flow mode.
   Kept as CSS rather than JS unmounting so switching modes never re-downloads
   anything the visitor already has. Playback is separately gated by the
   IntersectionObservers in inspiration.js, so a hidden section decodes nothing. */
.app.is-flow .hero,
.app.is-flow .insp { display: none; }
.app.is-home .ambient { display: none; }

/* ── hero banner ────────────────────────────────────────────────────────────
   Background is pure CSS (two radial highlights over a diagonal base) — no
   image request for the largest element on the page. */
.hero {
  position: relative;
  overflow: hidden;
  border-radius: 20px;
  padding: 20px 28px;
  display: flex;
  align-items: center;
  gap: 24px;
  background:
    radial-gradient(120% 130% at 18% 115%, rgba(186, 160, 255, .5) 0%, transparent 55%),
    radial-gradient(90% 110% at 88% -10%, rgba(120, 160, 255, .42) 0%, transparent 50%),
    linear-gradient(125deg, #4c2fd0 0%, #6a41ee 48%, #5b8cff 100%);
  box-shadow: 0 26px 70px -34px rgba(92, 60, 220, .9);
}

.hero__copy { flex: 1 1 auto; min-width: 0; }

.hero__title {
  margin: 0 0 7px;
  font-size: clamp(1.3rem, 2.2vw, 1.75rem);
  line-height: 1.1;
  font-weight: 800;
  letter-spacing: -.02em;
  text-transform: uppercase;
  color: #fff;
  text-wrap: balance;
}

.hero__sub {
  margin: 0 0 12px;
  max-width: 56ch;
  font-size: .86rem;
  line-height: 1.4;
  color: rgba(255, 255, 255, .8);
}

.btn--hero {
  background: #fff;
  color: #241a4d;
  border: none;
  font-weight: 650;
  padding: 10px 18px;
  border-radius: 999px;
  cursor: pointer;
  font-family: inherit;
  font-size: .93rem;
  transition: transform .15s ease, box-shadow .15s ease;
  box-shadow: 0 10px 26px -12px rgba(0, 0, 0, .6);
}
.btn--hero:hover { transform: translateY(-1px); box-shadow: 0 16px 32px -14px rgba(0, 0, 0, .7); }
.btn--hero:focus-visible { outline: 3px solid rgba(255, 255, 255, .7); outline-offset: 2px; }

/* ── marquee ticker (hero) + ambient carousel (flow) ────────────────────────
   Both are the same mechanism: one flex row, the clip list duplicated once, a
   single transform keyframe. Copy 2 is already in position when copy 1 scrolls
   out, so the loop is seamless with no JS scheduling at all.
   Edges feather with a real mask, not an overlay div, so whatever is behind
   shows through correctly. */
.ticker {
  flex: 0 0 auto;
  overflow: hidden;
  max-width: 46%;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}

.ticker__row {
  display: flex;
  /* This gap and .ticker__item's width below are mirrored in inspiration.js's
     mountTicker() call (opts.gap / opts.width), which needs them to hold a constant
     scroll speed. Keep the three in step. */
  gap: 8px;
  width: max-content;
  animation: ticker-slide var(--ticker-duration, 42s) linear infinite;
  will-change: transform;
}
/* -50% is exactly one copy of the duplicated list, which is what makes the
   restart invisible. */
@keyframes ticker-slide {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
.ticker:hover .ticker__row { animation-play-state: paused; }

.ticker__item {
  flex: 0 0 auto;
  width: 72px;
  aspect-ratio: 9 / 16;
  border-radius: 10px;
  overflow: hidden;
  background: rgba(0, 0, 0, .35) center / cover no-repeat;
  box-shadow: 0 8px 20px -10px rgba(0, 0, 0, .8);
}
.ticker__item video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ── ambient carousel (wizard flow) ─────────────────────────────────────────
   Atmosphere only: dimmed, blurred slightly, never interactive, never audible.
   pointer-events:none is load-bearing — it must never steal a click from the
   card sitting above it. */
.ambient {
  position: relative;
  /* full-bleed out of a centred column. Equal margins on BOTH sides, not
     left:50% + translateX(-50%): that trick assumes the box overflows to the right,
     and in an RTL page (Urdu) it overflowed 600px off the left edge instead. */
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  margin-top: 26px;
  overflow: hidden;
  pointer-events: none;
  opacity: .34;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 18%, #000 82%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 18%, #000 82%, transparent);
}
.ambient__row {
  display: flex;
  gap: 14px;
  width: max-content;
  animation: ticker-slide var(--ambient-duration, 88s) linear infinite;
  will-change: transform;
}
.ambient__item {
  flex: 0 0 auto;
  width: 128px;
  aspect-ratio: 9 / 16;
  border-radius: 12px;
  overflow: hidden;
  background: #10121a center / cover no-repeat;
  filter: saturate(.85);
}
.ambient__item video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ── inspiration grid ───────────────────────────────────────────────────────
   A JUSTIFIED grid, which is what the reference actually does (measure its
   screenshot: every row is the same height while tile widths differ, so it is
   not equal-width masonry). Each row has a fixed height and a tile's width is
   height x its aspect ratio; flex-grow then stretches the row to a clean right
   edge, and object-fit:cover absorbs the last few percent by cropping instead of
   distorting.

   The build step already normalized every clip into one of a few ratio classes
   (pipeline/inspiration.py), so --ar is one of a small known set and rows always
   resolve tidily however mixed the source material was. */
.insp { width: 100%; }

.insp__head {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.insp__title { margin: 0; font-size: 1.3rem; font-weight: 700; letter-spacing: -.02em; }
.insp__sub { margin: 0; color: var(--muted); font-size: .9rem; }

.insp__grid {
  --row-h: 300px;
  --gap: 12px;
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap);
}
/* Absorbs the leftover space on the LAST row, so three trailing tiles don't
   stretch to a third of the screen each. Zero-width, so it affects nothing else. */
.insp__grid::after { content: ""; flex: 999 999 auto; }

.insp__sentinel { height: 1px; }

.tile {
  position: relative;
  height: var(--row-h);
  /* --item-ar is set inline per tile from its ratio class; --ar is what the layout
     actually uses, so a breakpoint can override the shape without touching the
     per-item value (see the phone rules, which force a uniform portrait grid). */
  --ar: var(--item-ar, .5625);
  /* basis = the tile's ideal width; grow justifies the row; the bounds stop any
     single tile from being stretched or squeezed far enough to look wrong. */
  flex: 1 1 calc(var(--row-h) * var(--ar));
  min-width: calc(var(--row-h) * var(--ar) * .78);
  max-width: calc(var(--row-h) * var(--ar) * 1.5);
  border-radius: 14px;
  overflow: hidden;
  background: var(--card-2) center / cover no-repeat;
  border: 1px solid var(--border);
  cursor: pointer;
  isolation: isolate;
  transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.tile:hover {
  transform: translateY(-2px);
  border-color: #33384a;
  box-shadow: 0 18px 40px -22px rgba(0, 0, 0, .9);
}
.tile:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }

.tile__media { position: absolute; inset: 0; }
.tile__media video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: transparent;
}
/* The click-to-listen element is stacked UNDER the silent loop and revealed only
   once it is actually playing, so the swap never shows a black frame. */
.tile__full { position: absolute; inset: 0; z-index: 1; }
.tile__full video { width: 100%; height: 100%; object-fit: cover; }

/* ── play affordance ────────────────────────────────────────────────────── */
.tile__play {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-items: center;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  opacity: 0;
  transition: opacity .16s ease;
}
.tile:hover .tile__play,
.tile.is-audible .tile__play,
.tile__play:focus-visible { opacity: 1; }

.tile__play-dot {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(13, 14, 18, .55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(255, 255, 255, .22);
  box-shadow: 0 10px 26px -12px rgba(0, 0, 0, .9);
  transition: transform .16s ease, background .16s ease;
}
.tile__play:hover .tile__play-dot { transform: scale(1.07); background: rgba(124, 92, 255, .72); }
.tile__play-dot svg { display: block; margin-left: 2px; }   /* optical centring of a triangle */

/* while a tile is the audible one, the button becomes PAUSE and stays visible */
.tile.is-audible .tile__play-dot { background: rgba(124, 92, 255, .8); }

.tile__spinner {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: none;
  place-items: center;
  background: rgba(13, 14, 18, .3);
}
.tile.is-buffering .tile__spinner { display: grid; }
.tile__spinner span {
  width: 26px; height: 26px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .25);
  border-top-color: #fff;
  animation: tile-spin .7s linear infinite;
}
@keyframes tile-spin { to { transform: rotate(360deg); } }

/* ── "Create video" bar on a feathered darkening ────────────────────────── */
.tile__foot {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 3;
  padding: 26px 10px 10px;
  background: linear-gradient(to top, rgba(8, 9, 13, .88) 22%, rgba(8, 9, 13, .55) 55%, transparent);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .18s ease, transform .18s ease;
}
.tile:hover .tile__foot,
.tile:focus-within .tile__foot,
.tile.is-audible .tile__foot { opacity: 1; transform: none; }
/* Touch devices have no hover: a bar that only appears on hover is invisible
   there, so show it permanently instead. */
@media (hover: none) {
  .tile__foot { opacity: 1; transform: none; }
  .tile__play { opacity: 1; }
}

.tile__create {
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 9px;
  font-family: inherit;
  font-size: .8rem;
  font-weight: 650;
  color: #fff;
  cursor: pointer;
  background: var(--accent-grad);
  box-shadow: 0 8px 20px -10px rgba(124, 92, 255, .9);
  transition: filter .15s ease, transform .15s ease;
}
.tile__create:hover { filter: brightness(1.08); transform: translateY(-1px); }
.tile__create:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

.tile__meta {
  display: block;
  margin-bottom: 7px;
  font-size: .72rem;
  line-height: 1.3;
  color: rgba(255, 255, 255, .78);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.tile__len {
  position: absolute;
  top: 8px; right: 8px;
  z-index: 3;
  padding: 2px 7px;
  border-radius: 999px;
  font-size: .68rem;
  font-weight: 600;
  color: #fff;
  background: rgba(8, 9, 13, .62);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

/* ── "How it works?" modal ──────────────────────────────────────────────── */
.modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 20px;
}
.modal[hidden] { display: none; }

.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(6, 7, 10, .78);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  animation: modal-fade .16s ease-out;
}
@keyframes modal-fade { from { opacity: 0; } to { opacity: 1; } }

.modal__panel {
  position: relative;
  width: 100%;
  max-width: 760px;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 26px 28px 24px;
  animation: modal-rise .2s cubic-bezier(.2, .8, .3, 1);
}
@keyframes modal-rise {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to   { opacity: 1; transform: none; }
}

.modal__close {
  position: absolute;
  top: 12px; right: 14px;
  width: 34px; height: 34px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .06);
  color: var(--muted);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.modal__close:hover { background: rgba(255, 255, 255, .12); color: var(--text); }
.modal__close:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }

.modal__title {
  margin: 0 0 16px;
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: -.02em;
}

/* Fixed 16:9 frame, so dropping the real explainer in later needs no layout work. */
.modal__video {
  aspect-ratio: 16 / 9;
  width: 100%;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: #0b0c11;
  border: 1px solid var(--border);
}
.modal__video video,
.modal__video iframe { width: 100%; height: 100%; display: block; border: 0; }

.modal__placeholder {
  width: 100%; height: 100%;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 10px;
  color: #5d6270;
  background:
    radial-gradient(60% 80% at 50% 40%, rgba(124, 92, 255, .12), transparent 70%),
    repeating-linear-gradient(135deg, rgba(255,255,255,.02) 0 10px, transparent 10px 20px);
}
.modal__placeholder p { margin: 0; font-size: .9rem; }

.modal__steps {
  margin: 18px 0 0;
  padding-left: 20px;
  color: var(--muted);
  font-size: .92rem;
  line-height: 1.6;
}
.modal__steps li { margin-bottom: 6px; }

/* ── two-column step (preview beside the controls) ──────────────────────── */
.step-split { display: flex; gap: 22px; align-items: flex-start; }
.step-split__body { flex: 1 1 auto; min-width: 0; }
/* Left-align the copy when it sits beside a preview -- centred headings read as
   off-centre once they no longer own the full card width. */
.step-split__body .card__title,
.step-split__body .card__subtitle { text-align: left; }
.step-split__body .card__subtitle { margin-bottom: 18px; }

/* The preview column is sized by the clip's own aspect (set inline as --pv-ar), so a
   portrait Short and a landscape recitation both sit in a sensibly-shaped box rather
   than one being letterboxed into the other's frame. */
.step-split__media {
  flex: 0 0 auto;
  width: clamp(140px, 34%, 210px);
  aspect-ratio: var(--pv-ar, 9 / 16);
  max-height: 300px;
  border-radius: var(--radius-md);
  overflow: hidden;
  position: relative;
  background: var(--card-2) center / cover no-repeat;
  border: 1px solid var(--border);
}
.step-split__media[hidden] { display: none; }
.step-split__media video,
.step-split__media img,
.step-split__media iframe {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  border: 0;
}
/* A landscape source gets a wider, shorter box -- no point reserving a tall column. */
.step-split__media.is-landscape { width: clamp(180px, 42%, 300px); }

.pv-play {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  border: 0;
  padding: 0;
  background: linear-gradient(to top, rgba(8,9,13,.5), transparent 60%);
  cursor: pointer;
  color: #fff;
}
.pv-play span {
  width: 44px; height: 44px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(13, 14, 18, .6);
  border: 1px solid rgba(255, 255, 255, .24);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition: transform .15s ease, background .15s ease;
}
.pv-play:hover span { transform: scale(1.07); background: rgba(124, 92, 255, .75); }
.pv-play:focus-visible { outline: 2px solid var(--accent-2); outline-offset: -3px; }

@media (max-width: 620px) {
  /* Stack on a phone: side-by-side would leave the language combo too narrow to
     read the translator names in. */
  .step-split { flex-direction: column; align-items: stretch; }
  .step-split__media {
    width: 100%;
    max-width: 190px;
    margin: 0 auto;
    max-height: 240px;
  }
  .step-split__media.is-landscape { width: 100%; max-width: 100%; }
}

/* ── review-step preview of a picked showcase clip ──────────────────────── */
.insp-review {
  margin: 0 auto 18px;
  width: 172px;
  aspect-ratio: 9 / 16;
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--card-2) center / cover no-repeat;
}
.insp-review video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ── motion / data preferences ──────────────────────────────────────────────
   Reduced motion stops the marquees outright (inspiration.js additionally skips
   autoplay, so tiles stay on their posters until pressed). */
@media (prefers-reduced-motion: reduce) {
  .ticker__row, .ambient__row { animation: none; }
  .tile, .tile__foot, .tile__play, .btn--hero { transition: none; }
  .tile__spinner span { animation-duration: 2s; }
}

/* ── responsive ─────────────────────────────────────────────────────────────
   One dial does most of the work: --row-h. Everything in the grid is derived
   from it, so a breakpoint only has to restate that one value. */
/* Row height goes UP as the screen narrows, which is deliberate: it yields fewer,
   larger columns. Small tiles on a phone are both unreadable and expensive -- a
   3-across grid puts ~14 clips on screen at once, so the device least able to spare
   decoders is asked for the most of them. Two big tiles beat three cramped ones. */
@media (max-width: 1180px) { .insp__grid { --row-h: 280px; } }
@media (max-width: 900px)  { .insp__grid { --row-h: 300px; } }

@media (max-width: 860px) {
  .shell:has(.app.is-home) { padding: 14px 14px 48px; }
  .app.is-home { gap: 22px; }
  .hero {
    flex-direction: column;
    align-items: flex-start;
    padding: 24px 20px;
    min-height: 0;
    border-radius: 18px;
  }
  .hero__sub { max-width: none; font-size: .88rem; }
  .ticker { max-width: 100%; width: 100%; }
  .insp__title { font-size: 1.15rem; }
}

@media (max-width: 700px) {
  .insp__grid { --row-h: 270px; --gap: 9px; }
  .tile { border-radius: 12px; }
  .tile__create { font-size: .74rem; padding: 9px 8px; }   /* stays a 40px+ target */
  .tile__meta { -webkit-line-clamp: 1; font-size: .68rem; }
  .tile__play-dot { width: 46px; height: 46px; }
  .insp__head { margin-bottom: 12px; gap: 6px; }
  .insp__sub { font-size: .82rem; }
}

@media (max-width: 560px) {
  /* Below this, EVERY tile is laid out as 9:16 regardless of its own ratio class,
     and object-fit:cover crops the squares and the landscape clip to match. A
     justified grid needs several tiles per row to look justified; at phone width a
     1:1 tile is as wide as the whole column, so honouring real aspect ratios here
     produces rows of one and a wall that reads as broken. Uniform beats faithful at
     this size -- and cropping the preview is the same trade the build step already
     makes. */
  .tile { --ar: .5625; }
}

@media (max-width: 460px) {
  /* Two portrait tiles per row at this width, which is the densest layout that
     still leaves the play button and Create bar comfortably tappable. */
  .insp__grid { --row-h: 290px; }
  .hero__title { font-size: 1.35rem; }
  .ambient__item { width: 92px; }
}

/* ── Telegram Mini App ──────────────────────────────────────────────────────
   A narrow webview, usually on mobile data. Same components, tighter: no hero
   ticker, a compact hero, and a grid sized for a phone held in one hand. */
body.is-miniapp .app.is-home { gap: 16px; max-width: 100%; }
body.is-miniapp .hero { padding: 18px 16px; border-radius: 16px; }
body.is-miniapp .hero__title { font-size: 1.25rem; }
body.is-miniapp .hero__sub { display: none; }        /* the headline carries it */
body.is-miniapp .insp__grid { --row-h: 176px; --gap: 8px; }
body.is-miniapp .insp__sub { display: none; }
body.is-miniapp .ambient { margin-top: 18px; }
