/* ============================================================================
   PowerBoxDJ — the picture stages
   ----------------------------------------------------------------------------
   One component, two sizes, so the style carries from the home page into the
   feature pages:

     .hero-slider          full bleed at the top of a page. Pictures cross-fade on a
                           timer, headline over the top, < dots > at the foot.

     .hero-slider.inset    the same thing inside a section, sized by the screenshot
                           aspect, captioned as a numbered step.

   Only opacity and transform, driven by a class the script toggles, so they run the
   same in Chrome, Edge and Firefox on Windows with no polyfill and nothing
   experimental. Screenshots are 3840x2064; they are never handed to layout at that
   size, only cropped by object-fit.
   ========================================================================== */

/* ─────────────────── the hero slider ─────────────────── */

.hero-slider {
  position: relative; overflow: hidden;
  background: #000;
  height: clamp(420px, 76vh, 860px);
  border-bottom: 1px solid var(--line-soft);
}

.hs-track { position: absolute; inset: 0; }

/* THE PICTURES SLIDE. They used to cross-fade, which reads as one picture being swapped
   for another rather than as a strip of them being moved along.

   Each slide is still absolutely positioned rather than laid out in a long track, because
   a track has to rewind the whole width when it wraps from the last slide to the first,
   and that rewind is the one movement in a loop that looks like a mistake. Instead the
   outgoing slide leaves on one side while the incoming one arrives from the other, so
   forwards is always forwards however many slides there are.

   pre-left / pre-right park the incoming slide off stage with no transition; js/stage.js
   forces a reflow between that and .on so the arrival animates. */
.hs-slide {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  visibility: hidden; transform: translate3d(100%,0,0);
  /* The travel carries a fade with it: the arriving picture comes up to full as it lands and the
     leaving one drops back as it goes, which is what makes the movement read as a strip being
     pulled along rather than two pictures crossing over each other. */
  transition: transform .78s cubic-bezier(.4,0,.2,1), opacity .62s ease;
  will-change: transform, opacity; backface-visibility: hidden;
}
.hs-slide.on, .hs-slide.out-left, .hs-slide.out-right,
.hs-slide.pre-left, .hs-slide.pre-right { visibility: visible; }

/* Parked off stage: dimmed and with no transition, so the arrival has somewhere to come from. */
.hs-slide.pre-left  { transition: none; transform: translate3d(-100%,0,0); opacity: .3; }
.hs-slide.pre-right { transition: none; transform: translate3d(100%,0,0);  opacity: .3; }

.hs-slide.on        { transform: translate3d(0,0,0); opacity: 1; z-index: 2; }
.hs-slide.out-left  { transform: translate3d(-100%,0,0); opacity: .3; z-index: 1; }
.hs-slide.out-right { transform: translate3d(100%,0,0);  opacity: .3; z-index: 1; }

/* Too big: shrink to the frame. Too small: leave native size. Never scale up.
   contain fills the box (that is zoom). scale-down is contain or none, whichever is smaller. */
.hero-slider .hs-slide > img {
  position: relative; flex: 0 0 auto;
  margin: auto;
  align-self: center;
  width: auto !important; height: auto !important;
  max-width: 100%; max-height: 100%;
  object-fit: scale-down !important; object-position: center;
  transform: none !important; animation: none !important;
}
.hs-slide img {
  position: relative; flex: 0 0 auto;
  margin: auto;
  align-self: center;
  width: auto; height: auto;
  max-width: 100%; max-height: 100%;
  object-fit: scale-down; object-position: center;
  transform: none;
}
.hs-slide video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain; object-position: center;
  transform: none;
}
.hs-slide.on img { animation: none; transform: none; }

/* Coming-soon placeholder: the whole poster (box + words), never cropped to the frame. */
.hs-slide.hs-soon img,
.hs-slide.mm-blank img {
  object-fit: scale-down; object-position: center; transform: none;
}
.hs-slide.hs-soon.on img,
.hs-slide.mm-blank.on img { animation: none; transform: none; }

/* A wash under the words. These are screenshots, not photographs — busy, mid-grey and
   full of small text — so it has to be heavier than a photo would need, or the headline
   sits in the middle of a spreadsheet. */
.hs-slide::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top,
    rgba(0,0,0,.94) 0%, rgba(0,0,0,.82) 22%, rgba(0,0,0,.55) 45%,
    rgba(0,0,0,.22) 70%, rgba(0,0,0,.05) 88%, transparent 100%);
}

/* ── THE SLIDE'S OWN TEXT: CENTRED, ALONG THE TOP ──
   Each slide's name and line sit across the top of the hero, centred, and the control bar keeps the
   bottom to itself. No wrap and no measure: the line is as wide as it is and it is not squeezed into
   the 1200px column or wrapped at 46 characters. */
.hs-over {
  position: absolute; left: 0; right: 0; top: 0; bottom: auto; z-index: 3;
  padding: 30px 0 0;
}
.hs-over .wrap { display: block; max-width: none; }
.hs-cap { max-width: none; text-align: center; }
.hs-cap h1, .hs-cap h2 {
  margin: 0; color: #fff;
  font-size: clamp(1.35rem, 2.6vw, 2.3rem); font-weight: 700;
  letter-spacing: -.03em; line-height: 1.1;
  text-shadow: 0 2px 24px rgba(0,0,0,.85);
}
.hs-cap p {
  margin: .5em 0 0; color: rgba(255,255,255,.9);
  font-size: clamp(.9rem, 1.15vw, 1.08rem); line-height: 1.45;
  text-shadow: 0 1px 16px rgba(0,0,0,.9);
}

/* Home hero: H1 top-center. H2 directly under it, right-aligned, wraps at 45%. */
.hero-slider:not(.inset) .hs-over {
  inset: auto 0 auto 0; top: 0; padding: 28px 0 0;
}
.hero-slider:not(.inset) .hs-over .wrap {
  max-width: none; padding: 0 32px; width: 100%;
}
.hero-slider:not(.inset) .hs-cap {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.hero-slider:not(.inset) .hs-cap h1 {
  text-align: center;
  width: auto;
  max-width: 92%;
  color: #ffc900;
  -webkit-text-fill-color: #ffc900;
  text-shadow: none;
  background: rgba(0, 0, 0, .6);
  padding: .28em .55em;
}
.hero-slider:not(.inset) .hs-cap h2 {
  align-self: flex-end;
  width: auto;
  max-width: 45%;
  margin-top: .45em;
  text-align: left;
  font-size: clamp(1rem, 1.9vw, 1.55rem);
  font-weight: 400;
  color: #fff;
  background: rgba(0, 0, 0, .6);
  padding: .28em .55em;
  text-shadow: none;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.hero-slider:not(.inset) .hs-cap p {
  align-self: flex-end;
  width: auto;
  max-width: 45%;
  margin: .35em 0 0;
  text-align: left;
  color: #fff;
  background: rgba(0, 0, 0, .6);
  padding: .22em .55em;
  text-shadow: none;
}
.hero-slider:not(.inset) .hs-cap .hs-list {
  align-self: flex-end;
  width: max-content;
  max-width: none;
  margin: .45em 0 0;
  padding: .45em .7em .45em .7em;
  color: #fff;
  background: rgba(0, 0, 0, .6);
  font-size: clamp(1rem, 1.9vw, 1.55rem);
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
  text-shadow: none;
  white-space: nowrap;
}
.hero-slider:not(.inset) .hs-cap .hs-list li {
  margin: 0 0 .4em;
  color: inherit;
  font-size: inherit;
  white-space: nowrap;
}
.hero-slider:not(.inset) .hs-cap .hs-list li:last-child { margin-bottom: 0; }
.hero-slider:not(.inset) .hs-cap .hs-list li::before {
  background: var(--accent);
  top: .55em;
}
html[data-theme="light"] .hero-slider:not(.inset) .hs-cap h1 {
  background: rgba(255, 255, 255, .4);
}
html[data-theme="light"] .hero-slider:not(.inset) .hs-cap h2,
html[data-theme="light"] .hero-slider:not(.inset) .hs-cap p,
html[data-theme="light"] .hero-slider:not(.inset) .hs-cap .hs-list {
  color: #000;
  background: rgba(255, 255, 255, .4);
}
.hero-slider:not(.inset) .hs-slide .hs-cap > *,
.hero-slider:not(.inset) .hs-slide.on .hs-cap > * { transform: none; }

/* The wash under the words has to be at the top now, where they are. */
.hs-slide::after {
  background: linear-gradient(to bottom,
    rgba(0,0,0,.82) 0%, rgba(0,0,0,.55) 26%, rgba(0,0,0,.2) 52%,
    rgba(0,0,0,.28) 82%, rgba(0,0,0,.6) 100%);
}

@media (max-width: 860px) {
  .hs-over { padding-top: 16px; }
}
/* Words arrive with their slide. */
.hs-slide .hs-cap > * { opacity: 0; transform: translateY(16px); }
.hs-slide.on .hs-cap > * {
  opacity: 1; transform: none;
  transition: opacity .6s ease .18s, transform .7s cubic-bezier(.22,.68,0,1) .18s;
}
.hs-slide.on .hs-cap > * + * { transition-delay: .3s; }

/* The whole slide is the link. It sits under the caption, the dots and the arrows, and
   .hs-over is made transparent to the pointer so the words never block the click.
   Native video controls own the bottom strip, so the link stops short of them. */
.hs-hit { position: absolute; inset: 0; z-index: 2; display: block; }
.hs-slide:has(video[controls]) .hs-hit { bottom: 48px; }
.hs-slide:has(video[controls])::after { bottom: 48px; }
.hero-slider:not(.inset) .hs-hit { bottom: 0; }
.hero-slider:not(.inset) .hs-slide::after { bottom: 0; }
.hs-over { pointer-events: none; }
.hs-over a { pointer-events: auto; }

/* ── the control bar: < dots > ──
   One centred row at the foot of the slider, arrows flanking the dots, so the whole
   control reads as a single thing instead of three floating pieces. */
.hs-bar {
  position: absolute; left: 0; right: 0; bottom: 16px; z-index: 5;
  display: flex; align-items: center; justify-content: center; gap: 14px;
  opacity: 0; pointer-events: none;
  transition: opacity .16s ease;
}
.hero-slider:hover .hs-bar,
.hero-slider:focus-within .hs-bar,
.hero-slider.hs-expand .hs-bar {
  opacity: 1; pointer-events: auto;
}
.hero-slider:not(.inset) .hs-vid {
  position: absolute; left: 0; right: 0; bottom: 58px; z-index: 6;
  display: none;
  align-items: center; gap: 10px;
  padding: 6px 18px;
  pointer-events: auto;
}
.hero-slider:not(.inset):hover .hs-slide.on .hs-vid,
.hero-slider:not(.inset):focus-within .hs-slide.on .hs-vid { display: flex; }
.hs-vid-btn {
  flex: none; width: 32px; height: 32px; padding: 0;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid rgba(255,255,255,.3); background: rgba(0,0,0,.45); color: #fff;
}
.hs-vid-btn:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.hs-vid-btn svg { width: 16px; height: 16px; fill: currentColor; }
.hs-vid-prog { flex: 1 1 auto; height: 4px; margin: 0; cursor: pointer; accent-color: var(--accent); }
.hs-vid-vol { flex: 0 0 72px; height: 4px; margin: 0; cursor: pointer; accent-color: var(--accent); }
.hero-slider:not(.inset) .vid-load { bottom: 98px; }
.hs-arrow {
  width: 32px; height: 32px; padding: 0; flex: none;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid rgba(255,255,255,.3); border-radius: 0;
  background: rgba(0,0,0,.4); color: #fff;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  transition: background .16s ease, border-color .16s ease, color .16s ease;
}
.hs-arrow:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.hs-arrow svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }

.hs-dots { display: flex; align-items: center; gap: 9px; }
.hs-dots button {
  width: 8px; height: 8px; padding: 0; border-radius: 0; cursor: pointer;
  border: 1px solid rgba(255,255,255,.55); background: transparent;
  transition: background .2s ease, transform .2s ease, border-color .2s ease;
}
.hs-dots button:hover { background: rgba(255,255,255,.6); }
.hs-dots button.on { background: var(--accent); border-color: var(--accent); transform: scale(1.25); }

/* ── the count up to the next slide ──
   NEVER REMOVE THIS BAR. A line under the dots, as wide as that row, filling over the
   dwell time, so the wait belongs to the control it is counting for. It reaches the end
   and the next picture slides in. It stays after arrows and dots; it only pauses on hover.

   Driven by a CSS animation rather than a second timer: it cannot drift out of step with
   the one that actually advances the slide, and pausing it is one property. It fills left
   to right whichever way the slides are going — it is the time left, not a direction. */
.hs-dots-wrap { display: flex; flex-direction: column; align-items: stretch; gap: 6px; }
.hs-prog-row { display: flex; align-items: center; gap: 8px; }
.hs-prog {
  height: 3px; width: 100%; flex: 1 1 auto;
  background: rgba(255,255,255,.28); overflow: hidden;
}
.hs-toggle {
  flex: none; width: 12px; height: 12px; padding: 0;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid rgba(255,255,255,.3); border-radius: 0;
  background: rgba(0,0,0,.4); color: #fff;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.hs-toggle:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.hs-toggle svg { width: 6px; height: 6px; fill: currentColor; }
.hs-prog i { display: block; height: 100%; width: 0; background: var(--accent); }
.hs-prog.run i { animation: hsFill linear forwards; }
@keyframes hsFill { from { width: 0 } to { width: 100% } }

@media (max-width: 700px) {
  .hero-slider { height: clamp(380px, 64vh, 560px); }
  .hs-over { padding-bottom: 62px; }
  .hs-arrow { width: 30px; height: 30px; }
  .hs-bar { gap: 11px; bottom: 16px; }
}

/* ─────────────────── a list beside a strip ───────────────────
   The Features menu's pair, sized for a page: the list of what is in the strip on the left, one row
   each with a line of description, and the frame on the right. Bigger than the menu's — this is the
   thing the page is for, not a preview inside a panel.

   Rows are buttons because there is nowhere to go: they drive the strip beside them. .at is the row
   whose clip is up, marked by js/stage.js from the strip's own signal. */
.vid-strip { display: grid; grid-template-columns: minmax(220px, 340px) minmax(0, 1fr); gap: 26px; align-items: start; }
@media (max-width: 900px) { .vid-strip { grid-template-columns: 1fr; } }

.scratch-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start; }
.scratch-col { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.scratch-col .hero-slider.inset {
  border: 0; box-shadow: none; background: transparent;
  aspect-ratio: 411 / 226;
  max-width: min(100%, 411px);
}
.scratch-col .hs-iname { display: none; }
.scratch-col .hero-slider.hs-expand .hs-track { top: 0; }
@media (max-width: 900px) { .scratch-pair { grid-template-columns: 1fr; } }
table.scratch-modes { width: 100%; border: 1px solid var(--line); }
table.scratch-modes th, table.scratch-modes td { text-align: center; vertical-align: middle; font-family: inherit; font-size: inherit; letter-spacing: normal; font-weight: 400; color: var(--text-2); }
table.scratch-modes th { background: var(--panel-2); }
table.scratch-modes td:first-child { width: auto; }
table.scratch-modes th:last-child, table.scratch-modes td:last-child { text-align: left; }
table.scratch-modes td img { display: block; margin: 0 auto; }
[data-strip] table.scratch-modes tbody tr { cursor: pointer; }
[data-strip] table.scratch-modes tbody tr:hover td,
[data-strip] table.scratch-modes tbody tr.at td { background: var(--accent-softer); color: var(--text); }

.feat-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start; }
.feat-pair > .hero-slider,
.feat-pair > img,
.feat-pair > .lyrics-board { min-width: 0; max-width: 100%; height: auto; justify-self: start; }
.feat-pair > img { width: auto !important; height: auto !important; max-width: 100%; }
img.shot-cap {
  display: block;
  width: auto !important;
  height: auto !important;
}
.feat-pair .hero-slider.inset {
  border: 0; box-shadow: none; background: transparent;
  max-height: none; width: 100%;
}
.feat-pair .hs-iname { display: none; }
.feat-pair .hero-slider.hs-expand .hs-track { top: 0; }
.lyrics-board { position: relative; width: 100%; line-height: 0; }
.lyrics-board > img { display: block; width: 100%; height: auto; vertical-align: top; }
.lyrics-map ul.ticks li { cursor: pointer; }
.lyrics-map ul.ticks li:hover,
.lyrics-map ul.ticks li.at { color: var(--text); }
.lyrics-map ul.ticks li:hover::before,
.lyrics-map ul.ticks li.at::before { background: var(--accent); }
[data-strip] .lede[data-slide] { cursor: pointer; }
.lede img {
  width: auto; height: auto;
  max-width: 100%;
  vertical-align: middle;
  transform: none;
}
.tut-row {
  display: grid;
  grid-template-columns: minmax(0,1fr);
  gap: 18px;
  align-items: start;
  margin: 0 0 .8em;
}
.tut-row:has(> img),
.tut-row:has(.tut-pics) {
  grid-template-columns: minmax(0,1fr) auto;
}
.tut-row.tut-stack {
  grid-template-columns: minmax(0,1fr);
}
.tut-row .lede { margin-bottom: 0; }
.tut-row > img,
.tut-row .tut-pics { justify-self: start; }
.tut-pics { display: flex; gap: 8px; align-items: start; }
.tut-row img { width: auto; height: auto; max-width: 100%; transform: none; }
.stem-tools {
  display: grid;
  grid-template-columns: minmax(0,1fr) auto;
  gap: 22px;
  align-items: start;
  margin-top: 8px;
}
.stem-board {
  position: sticky;
  top: max(72px, calc(50vh - 70.5px));
  width: 492px; max-width: 100%;
}
.stem-board > img { display: block; width: 100%; height: auto; }
.stem-hot {
  position: absolute;
  z-index: 1;
  box-shadow: inset 0 0 0 2px var(--gold);
  background: rgba(255,201,0,.22);
  opacity: 0;
  pointer-events: auto;
  cursor: pointer;
}
.stem-hot[data-tool="all"] { z-index: 0; pointer-events: none; }
.stem-hot-pic {
  box-shadow: none;
  background: none;
  height: auto;
  object-fit: contain;
  transform: none;
}
.stem-hot.on { opacity: 1; }
.stem-steps .tut-row { cursor: pointer; }
.stem-steps .tut-row:hover,
.stem-steps .tut-row.at { background: rgba(255,201,0,.06); }
@media (max-width: 900px) {
  .stem-tools { grid-template-columns: minmax(0,1fr); }
  .stem-board { position: static; }
}
[data-strip] .lede[data-slide]:hover,
[data-strip] .lede[data-slide].at { color: var(--text); }

.slip-reverse { display: flex; align-items: center; gap: 24px; }
.slip-reverse p { flex: 1; margin: 0; }
.slip-reverse img { flex: none; }

.vid-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
/* One line each, and the line is the row. */
.vid-list button {
  appearance: none; -webkit-appearance: none;
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: transparent; border: 0; border-left: 2px solid transparent;
  padding: 9px 12px; font-family: var(--sans);
  font-size: .92rem; font-weight: 400; color: var(--text);
  letter-spacing: -.01em; line-height: 1.4;
  transition: background .14s ease, border-color .14s ease, color .14s ease;
}
.vid-list button:hover,
.vid-list button.at { background: var(--accent-softer); border-left-color: var(--accent); color: var(--accent); }

/* The frame. Flat and black, its own shape from the clip, and the control bar sits on it. */
.vid-carousel.hero-slider.inset {
  aspect-ratio: 16 / 9; max-height: 72vh;
  margin: 0; padding: 0; border: 0; box-shadow: none; background: #000;
}
.vid-carousel .hs-slide img {
  width: auto; height: auto;
  max-width: 100%; max-height: 100%;
  object-fit: scale-down; object-position: center;
  transform: none;
}
.vid-carousel .hs-slide.on img { animation: none; transform: none; }
.vid-carousel .hs-slide::after { display: none; }

/* FULL APP, THEN A SLOW PUSH — hold the whole screenshot, then ease in so it
   reads as a clip rather than a still. .zoom-tr goes to the top right (language,
   theme). .zoom-c goes to the Media Viewer in the middle. */
.vid-carousel.zoom-tr,
.vid-carousel.zoom-c,
.vid-carousel.zoom-info { aspect-ratio: 3840 / 2064; }
.vid-carousel.zoom-tr .hs-slide img,
.vid-carousel.zoom-c .hs-slide img,
.vid-carousel.zoom-info .hs-slide img {
  object-fit: scale-down; object-position: center;
  transform: none;
}
.vid-carousel.zoom-tr .hs-slide img { transform-origin: 100% 0; }
.vid-carousel.zoom-c .hs-slide img { transform-origin: 50% 36%; }
.vid-carousel.zoom-info .hs-slide img { transform-origin: 20% 32%; }
.vid-carousel.zoom-tr .hs-slide.on img,
.vid-carousel.zoom-c .hs-slide.on img,
.vid-carousel.zoom-info .hs-slide.on img {
  animation: none;
  transform: none;
}
@keyframes hsZoomTR {
  0%, 12% { transform: scale(1); }
  72%, 88% { transform: scale(2.75); }
  100% { transform: scale(1); }
}
@keyframes hsZoomC {
  0%, 12% { transform: scale(1); }
  72%, 88% { transform: scale(2.2); }
  100% { transform: scale(1); }
}
/* A clip wider than the frame: pin the left edge and crop the right, then a bit more zoom. */
.hs-slide video.hs-left {
  object-fit: cover; object-position: left top;
  transform: scale(1); transform-origin: left top;
}

/* HOW MUCH HAS ARRIVED. A hairline at the foot of the frame, above the clip's own controls, showing
   what has downloaded so far. It is there while there is not enough to play through and gone the
   moment there is — see the loading bar in js/stage.js. */
.vid-load {
  position: absolute; left: 0; right: 0; bottom: 0; height: 3px; z-index: 4;
  background: rgba(255,255,255,.16);
  opacity: 0; transition: opacity .2s ease; pointer-events: none;
}
.hs-slide:has(video[controls]) .vid-load { bottom: 40px; }
.vid-load.on { opacity: 1; }
.vid-load i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .2s linear; }

/* ─────────────────── the inset slider ───────────────────
   The same component as the hero, contained inside a section: bordered, rounded, and
   sized by the screenshot's aspect rather than the viewport. Used for a tool that steps
   through states, so its caption is a numbered step instead of a headline. */
.hero-slider.inset {
  height: auto; aspect-ratio: 3840 / 2064;
  border: 1px solid var(--line); border-radius: 0;
  box-shadow: 0 24px 60px rgba(0,0,0,.42);
}
.hero-slider.inset .hs-slide img {
  width: auto; height: auto;
  max-width: 100%; max-height: 100%;
  object-fit: scale-down; object-position: center;
  transform: none;
}
.hero-slider.inset .hs-slide.on img { animation: none; transform: none; }
/* The inset one keeps its caption along the bottom left, where a numbered step belongs. Only the
   hero's text moved to the top right, so the base rules are put back here. */
.hero-slider.inset .hs-over { top: auto; bottom: 0; padding: 0 0 60px; }
.hero-slider.inset .hs-over .wrap { display: block; }
.hero-slider.inset .hs-cap { max-width: none; padding: 0 30px; text-align: left; }
.hero-slider.inset .hs-slide::after {
  background: linear-gradient(to top,
    rgba(0,0,0,.94) 0%, rgba(0,0,0,.8) 24%, rgba(0,0,0,.45) 50%, transparent 84%);
}
.hero-slider.inset .hs-cap h3 {
  margin: 0; color: #fff;
  font-size: clamp(1.15rem, 2.2vw, 1.85rem); font-weight: 730;
  letter-spacing: -.03em; line-height: 1.1;
  text-shadow: 0 2px 20px rgba(0,0,0,.8);
}
.hs-n {
  display: block; margin-bottom: 7px;
  font-family: var(--mono); font-size: .66rem; letter-spacing: .18em;
  color: var(--accent); text-shadow: 0 1px 10px rgba(0,0,0,.9);
}
.hero-slider.inset .hs-slide .hs-cap > * { opacity: 0; transform: translateY(12px); }
.hero-slider.inset .hs-slide.on .hs-cap > * {
  opacity: 1; transform: none;
  transition: opacity .5s ease .12s, transform .55s cubic-bezier(.22,.68,0,1) .12s;
}
.hero-slider.inset .hs-slide.on .hs-cap > * + * { transition-delay: .22s; }

/* ─────────────── a section's own carousel ───────────────
   The same component a third time: inside a section, beside the copy, and sized by the WIDTH it is
   given and nothing else. There is no height here and there is no pixel anywhere — the frame takes
   the shape of the first file that loads (data-shape in js/stage.js) and its height follows from the
   width of the column, so it grows and shrinks with the window. 3/2 is only what it is until that
   first file arrives.

   CONTAINED, NEVER CROPPED, AND NO DRIFT — the same two rules the mega menu's frames follow, for the
   same reason: these are screenshots with their own text drawn on them, cropping cuts it off, and a
   drift is a scale, which on a contained picture is a crop.

   THE WASH IS ONLY UNDER WORDS. A dark gradient across the foot of a picture that has no caption on
   it is a shadow with nothing to explain it, so it is drawn for the slides that carry a caption and
   for no others — see .has-cap, set by folderSlide. */
.sec-carousel.hero-slider.inset { aspect-ratio: 3 / 2; max-height: 82vh; }
.fplayer-carousel.hero-slider.inset { aspect-ratio: 1597 / 1180; max-width: min(100%, 1597px); }
.hotcues-carousel.hero-slider.inset { aspect-ratio: 997 / 420; max-width: min(100%, 997px); }
.loop-carousel.hero-slider.inset { aspect-ratio: 557 / 167; max-width: min(100%, 557px); }
.slip-reverse-carousel.hero-slider.inset { aspect-ratio: 71 / 51; max-width: min(100%, 71px); }
.stems-carousel.hero-slider.inset { aspect-ratio: 1557 / 1024; max-width: min(100%, 1557px); }
.beat-slicer-carousel.hero-slider.inset { aspect-ratio: 1377 / 91; max-width: min(100%, 1377px); max-height: none; }
.outputs-carousel.hero-slider.inset { aspect-ratio: 772 / 537; max-width: min(100%, 772px); }
.equalizers-carousel.hero-slider.inset { aspect-ratio: 762 / 256; max-width: min(100%, 762px); }
.pitch-shift-carousel.hero-slider.inset { aspect-ratio: 375 / 88; max-width: min(100%, 375px); }
.tempo-shift-carousel.hero-slider.inset { aspect-ratio: 375 / 95; max-width: min(100%, 375px); }
.gain-carousel.hero-slider.inset { aspect-ratio: 370 / 101; max-width: min(100%, 370px); }
.lyrics-carousel.hero-slider.inset { aspect-ratio: 1600 / 1094; max-width: min(100%, 1600px); }
.karaokee-carousel.hero-slider.inset { aspect-ratio: 1520 / 241; max-width: min(100%, 1520px); }
.soundwave-carousel.hero-slider.inset { aspect-ratio: 1600 / 474; max-width: min(100%, 1600px); }
.dynamic-soundwave-carousel.hero-slider.inset { aspect-ratio: 1519 / 301; max-width: min(100%, 1519px); }
.music-signature-carousel.hero-slider.inset { aspect-ratio: 335 / 54; max-width: min(100%, 335px); max-height: none; }
.feat-pair .music-signature-carousel.hero-slider.inset {
  align-self: stretch;
  width: 100%;
  max-width: none;
  aspect-ratio: auto;
  height: auto;
  min-height: 54px;
}
.music-signature-carousel .hs-slide {
  display: flex;
  align-items: center;
  justify-content: center;
}
.music-signature-carousel .hs-slide img {
  margin: auto;
  align-self: center;
}
.beat-juggle-carousel.hero-slider.inset { aspect-ratio: 1004 / 177; max-width: min(100%, 1004px); }
.beat-sync-carousel.hero-slider.inset { aspect-ratio: 181 / 168; max-width: min(100%, 181px); }
.international-carousel.hero-slider.inset { aspect-ratio: 1056 / 746; max-width: min(100%, 1056px); }
.theme-carousel.hero-slider.inset { aspect-ratio: 359 / 407; max-width: min(100%, 359px); }
.sec-carousel .hs-slide img,
.sec-carousel .hs-slide video {
  width: auto; height: auto;
  max-width: 100%; max-height: 100%;
  object-fit: scale-down; object-position: center;
  transform: none; animation: none;
}
.sec-carousel .hs-slide.on img { animation: none; transform: none; }
.sec-carousel .hs-slide::after { display: none; }
.sec-carousel .hs-slide.has-cap::after { display: block; }

/* ── click the picture, the strip takes the page ──
   Not the browser's fullscreen: the same slider, moved onto a dim, filling almost the
   window. Images are contained so a screenshot is not cropped just because it got bigger.
   The placeholder holds the hole it left in the layout until it comes back. */
body.hs-lock { overflow: hidden; }
.hs-ph { visibility: hidden; pointer-events: none; }
.hs-scrim {
  position: fixed; inset: 0; z-index: 190;
  background: rgba(0,0,0,.88);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.hs-scrim[hidden] { display: none; }

.hero-slider.hs-expand {
  position: fixed !important;
  inset: 3vh 3vw 7vh !important;
  z-index: 200;
  width: auto !important; height: auto !important;
  max-width: none !important; max-height: none !important;
  min-width: 0 !important;
  flex: none !important;
  aspect-ratio: auto !important;
  margin: 0 !important;
  padding-top: 52px;
  border: 1px solid var(--line);
  box-shadow: 0 24px 80px rgba(0,0,0,.6);
}
.hero-slider.hs-expand .hs-track { top: 52px; }
.hero-slider.hs-expand .hs-slide img {
  position: relative;
  width: auto; height: auto;
  max-width: 100%; max-height: 100%;
  object-fit: scale-down; object-position: center;
  transform: none; animation: none;
}
.hero-slider.hs-expand .hs-slide.on img { animation: none; transform: none; }
.hero-slider.hs-expand .hs-slide video.hs-left {
  object-fit: contain; object-position: center;
  transform: none;
}
.hero-slider.hs-expand .hs-hit { pointer-events: none; }

.hs-x {
  display: none;
  position: absolute; top: 10px; right: 12px; z-index: 8;
  width: 40px; height: 40px; padding: 0;
  place-items: center; cursor: pointer;
  border: 0; background: transparent;
  color: #fff; font-size: 2rem; line-height: 1;
  text-shadow: 0 1px 12px rgba(0,0,0,.9);
}
.hs-x:hover { color: var(--accent); }
.hero-slider.hs-expand .hs-x { display: grid; top: 6px; }

.hs-zoom {
  position: absolute; top: 10px; right: 12px; z-index: 8;
  width: 36px; height: 36px; padding: 0;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid rgba(255,255,255,.3); border-radius: 0;
  background: rgba(0,0,0,.4); color: #fff;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  opacity: 0; pointer-events: none;
  transition: opacity .16s ease;
}
.hs-zoom svg { width: 18px; height: 18px; }
.hs-zoom:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.hero-slider:hover .hs-zoom,
.hero-slider:focus-within .hs-zoom { opacity: 1; pointer-events: auto; }
.hero-slider.hs-expand .hs-zoom { display: none; }

.hs-iname {
  display: none;
  position: absolute; left: 0; right: 52px; top: 0; z-index: 8;
  height: 52px; padding: 0 18px;
  align-items: center;
  font-size: 1.08rem; font-weight: 650; letter-spacing: -.02em;
  color: #fff;
  pointer-events: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hero-slider.hs-expand .hs-iname { display: flex; }

.hero-slider:not(.hs-expand) { cursor: pointer; }
.hero-slider:not(.hs-expand) .hs-bar,
.hero-slider:not(.hs-expand) .hs-bar * { cursor: pointer; }
.hero-slider:not(.hs-expand) video[controls] { cursor: auto; }
/* Dots and arrows sit above the clip's own control bar, not on top of it. */
.hero-slider:has(.hs-slide.on video[controls]) .hs-bar { bottom: 52px; }

/* The note that stays true for every step of a sequence. */
.seq-note {
  display: flex; align-items: flex-start; gap: 9px;
  margin: 14px 0 0; padding: 11px 14px;
  border: 1px solid var(--line-soft); border-left: 2px solid var(--accent-dim);
  border-radius: 0; background: var(--accent-softer);
  font-size: .86rem; line-height: 1.5; color: var(--text-2);
}
.seq-note b { color: var(--text); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  /* THE SLIDER STILL SLIDES HERE. It used to be cut to .01ms, which is not a reduced animation,
     it is no animation: the pictures were replaced rather than moved, and a carousel that
     replaces reads as broken rather than as calm. Windows with "animation effects" switched off
     reports this setting, so that hard cut was what a good number of visitors actually saw.

     Shorter, not gone — half the travel time — and what does stay off is the decoration: the slow
     fifteen-second zoom on the picture, which is motion for its own sake. */
  .hs-slide { transition-duration: .42s, .34s !important; }
  .hs-slide .hs-cap > * { transition-duration: .3s !important; }
  .hs-slide.on img { animation: none; transform: none; }
  .vid-carousel.zoom-tr .hs-slide.on img,
  .vid-carousel.zoom-c .hs-slide.on img,
  .vid-carousel.zoom-info .hs-slide.on img { animation: none; transform: scale(1); }
}

/* ─────────────────── tool list ───────────────────
   What sits on the library, in columns. Items that already have a section are links;
   the rest are named until their pictures arrive. */
.tool-list {
  list-style: none; margin: 0; padding: 0;
  columns: 3; column-gap: 38px;
}
.tool-list li {
  break-inside: avoid;
  padding: 8px 0 8px 20px; position: relative;
  font-size: .96rem; line-height: 1.4; color: var(--text-2);
}
.tool-list li::before {
  content: ""; position: absolute; left: 0; top: 1.05em;
  width: 6px; height: 6px; border-radius: 0; background: var(--accent-dim);
}
.tool-list a { color: var(--text); text-decoration: none; border-bottom: 1px solid var(--accent-dim); }
.tool-list a:hover { color: var(--accent); border-bottom-color: var(--accent); }
@media (max-width: 900px) { .tool-list { columns: 2; } }
@media (max-width: 560px) { .tool-list { columns: 1; } }

/* ─────────────────── download row ───────────────────
   TWO COLUMNS, TWO THINGS EACH:
     left   the fast download, and the Windows warning under it
     right  the full offline build, and the clip under that

   EVERY ITEM IS ITS OWN HEIGHT. Nothing is stretched to match anything else — the panels are as tall
   as their words, the warning is as tall as its paragraphs.

   The clip is the single exception, and it is what makes the two columns finish level: its row is
   1fr, so it takes whatever height is left in its column after the offline panel has had what it
   needs. The video inside it is out of flow (see .dl-clip below), so the frame's own 464x688 never
   pushes back — the space decides the clip, not the file.

   Below 860px the four things simply stack in that order. */
/* ONE GAP, EVERYWHERE. The space between the two columns is the same as the space between the two
   panels stacked in each of them, so all four sit on one rhythm — see --dl-gap. */
/* THE RIGHT COLUMN IS AS WIDE AS THE OFFLINE PANEL'S CONTENTS. max-content: no dead space to the
   right of it, and no number in this file deciding its width either — the panel is its heading, its
   button and its meta line, and the column is exactly that. It does not move when the window does,
   because content does not change width; the fast download takes all the slack.

   THE CLIP TAKES ITS WIDTH FROM THE SAME PLACE. It is the other item in this column and grid items
   fill their column, so the video is the width of the panel under it, whatever that turns out to be.
   The video inside is out of flow, so it contributes nothing to that width. */
.dl-row {
  --dl-gap: 18px;
  display: grid; grid-template-columns: minmax(0, 1fr) max-content;
  gap: var(--dl-gap); align-items: stretch;
}
/* .dl-side, not .dl-col — .dl-col is already the two lists further down the page. */
.dl-side { display: grid; gap: var(--dl-gap); }
/* Left: each item at its own height, packed to the top, nothing filling the leftover. */
.dl-side-fast { align-content: start; }
/* Right: the clip first, taking whatever is left, then the panel at its own height. minmax(0,1fr)
   rather than 1fr so the row can be shorter than the video's intrinsic height instead of being
   floored by it. */
.dl-side-full { grid-template-rows: minmax(0, 1fr) auto; }

/* THE WHOLE PANEL IS THE DOWNLOAD — the panel is the <a>, so every pixel of it is the
   click, not just the button. It is big for the same reason: two files, two big targets.
   One picture each and it lives in the button, so the panel is a single column. */
/* BOTH PANELS CARRY THE THEME'S BORDER, and so does the clip under them: --accent-dim, which is the
   brush colour muted, so it is gold on the gold theme and teal on the teal one rather than a colour
   written into this file. Both are downloads; one being quieter than the other is a matter of size
   and of which button it wears, not of one being fenced off in grey. */
/* ONE PADDING, ALL FOUR SIDES, BOTH PANELS: left, right, top and bottom are the same 24px, and the
   same on the fast one as on the offline one. The two used to run 34/32 and 20/26, which is four
   different numbers for the same idea. */
.dl-pick {
  display: block;
  border: 1px solid var(--accent-dim); border-radius: 0;
  background: var(--panel); padding: 24px;
  color: inherit; text-decoration: none; cursor: pointer;
  transition: border-color .14s ease, background .14s ease, transform .12s ease;
}
.dl-pick:hover { border-color: var(--accent); background: var(--panel-2); color: inherit; }
.dl-pick:active { transform: translateY(1px); }
/* Reached by keyboard it has to say so, and it is one target so the ring goes round it. */
.dl-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }


/* Nothing on this page is bold. */
.dl-pick h3 { margin: 0 0 6px; font-size: 1.06rem; font-weight: 400; letter-spacing: -.02em; }
/* What the file actually is, in the panel that offers it — the difference between the two
   is the only thing anyone needs from this page.

   No measure on it: a max-width was breaking the line early and mid-sentence while the
   panel still had room to its right. It wraps to the column it is in, and a word with
   nowhere to break is broken rather than pushed through the side of the panel. */
.dl-pick .what {
  margin: 0 0 14px; font-size: .9rem; line-height: 1.5; color: var(--text-2);
  overflow-wrap: break-word; word-wrap: break-word;
}
.dl-pick .btn { font-weight: 400; pointer-events: none; }
.dl-pick .meta {
  margin: 14px 0 0; font-family: var(--mono); font-size: .72rem; color: var(--text-3);
}
/* The package in the button says what the file is: open and coming down for the one that
   fetches its parts, closed for the one that already holds them. Its own colours, not the
   button's, so it is never tinted by the gold behind it. */
.btn .btn-pkg { display: block; flex: none; margin: -6px 4px -6px -6px; object-fit: contain; }
.dl-pick.secondary .btn-pkg { width: 26px; height: 26px; margin: -4px 3px -4px -4px; }

/* THE BIG BUTTON, DEFINED ONCE. These numbers used to be written as .dl-pick.primary .btn-lg,
   which meant the only thing that could look like the installer button was the installer button.
   The bar's Download Now is meant to be the same button, so the rule is the button's own now and
   both places read from it. Same values as before, so nothing about download.html moves. */
.btn-lg { padding: 15px 26px; font-size: 1rem; }
.btn-lg .btn-pkg { width: 40px; height: 40px; }

/* ── which one to take ──
   The online installer is what almost everyone wants, so it is the panel that looks like
   an answer: bigger, brighter-edged, with the button that reads as the action. The offline
   build is the same target — the whole panel is still the link — drawn quieter, so the
   page offers a choice without asking anyone to make one. */
/* Border, background and padding are the same on both now — see .dl-pick. What is left is the size
   of the words and of the button, which is the only difference the page needs between the file
   almost everyone wants and the one for the case that needs it. */
.dl-pick.secondary h3 { font-size: .95rem; }
/* The button is as wide as what is in it. Nothing stretches it. */
.dl-pick.secondary .btn-sm { width: auto; padding: 9px 15px; font-size: .84rem; }
.dl-pick.secondary .meta { margin-top: 10px; }
/* The clip is decoration, not a control. pointer-events:none makes it invisible to hit
   testing entirely — no clicking, no right-click menu — and the cursor passes through.

   THE PANELS SET THE HEIGHT. THE CLIP TAKES IT.
   Never the other way round: the video is taken out of flow, so the height of its own frame — 720,
   1080, whatever the file is — contributes nothing to the row. The row is the two panels and the gap
   between them, full stop, and this column stretches to that. Left in flow, a frame taller than the
   panels would have set the row height and stretched the panels to match it, which is exactly
   backwards.

   NO BORDER: it is a picture, not a panel, so nothing is drawn round it. */
.dl-clip {
  position: relative; align-self: stretch; min-height: 0;
  border: 0; border-radius: 0; overflow: hidden;
  background: #000; line-height: 0;
  pointer-events: none; user-select: none; -webkit-user-select: none;
}
.dl-clip video {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%; margin: 0; pointer-events: none;
  object-fit: cover; object-position: center;
}

/* ── ONLY WHEN THERE IS ROOM FOR IT ──
   The clip has exactly one job: take the height the offline panel leaves in its column, so the two
   columns finish level. When that leftover is thin the job is not being done — object-fit:cover
   crops a portrait video to a horizontal band of somebody's shoulder, and the row reads better with
   the offline panel simply sitting at its own height.

   HOW MUCH IS ENOUGH IS MEASURED, NOT GUESSED. No media query can read a leftover grid track, and
   the leftover here is the left column's content minus the offline panel — which is prose, and moves
   with the wrap of one sentence at one window width. See the script at the foot of download.html; it
   is what puts .no-clip on the row, and this is what that class means.

   IT CANNOT OSCILLATE: the script measures the two panels and the warning, all three of which are
   content-sized and none of which depends on the clip being there, so hiding the clip cannot change
   the number that decided to hide it. */
.dl-row.no-clip .dl-clip { display: none; }
/* One item left in the column, so it is its own height at the top of it rather than stretched over
   the row the clip was holding open. */
.dl-row.no-clip .dl-side-full { grid-template-rows: auto; align-content: start; }

@media (max-width: 860px) {
  .dl-row { grid-template-columns: 1fr; }
  .dl-col-full { grid-template-rows: auto auto; }
  /* STACKED, THE CLIP HAS NO JOB AT ALL. There is no column beside it to finish level with, so it is
     not taking leftover height any more — it is a block of video wedged between the two downloads on
     the narrowest screens, where those two downloads are the whole of what the page is for. "Enough
     room" is exactly what there is not here, so it goes.

     In CSS and not in the script, so it holds with JavaScript off too. It used to go back into flow
     at max-width 420px and object-fit:contain; those two rules were what made it its own shape here
     and there is nothing left for them to shape. */
  .dl-clip { display: none; }
}

/* ── the version line ──
   Under the title, above everything else, because it is what a returning visitor opens
   this page to read. Mono and in the brush colour so the number itself is the thing the
   eye lands on, not the words in front of it. */
.dl-head { margin-bottom: 30px; }
.dl-ver { margin: 0 0 8px; font-size: .95rem; color: var(--text-2); }
.dl-ver a { margin-left: 12px; }
.dl-ver b {
  font-family: var(--mono); font-weight: 400; font-size: .95rem;
  color: var(--accent); letter-spacing: .01em;
}

/* ── what Windows is about to say ──
   Amber, never the brush: on the gold and amber themes an accent-coloured caution would
   read as decoration, and on the red theme it would read as an error. Left edge thicker
   than the rest of the border — the shape of a note, not of another panel to click. */
/* No margin of its own: it is a cell of the download grid now, and the gap between the four panels
   is the grid's to set. A 30px top margin here on top of an 18px gap is what made the space under
   the fast download bigger than every other space on the row. */
.dl-note {
  margin: 0;
  border: 1px solid var(--warn-line); border-left: 3px solid var(--warn);
  border-radius: 0; background: var(--warn-soft);
  padding: 20px 24px;
}
.dl-note h2 {
  margin: 0 0 8px; font-size: 1rem; font-weight: 640; color: var(--warn);
  display: flex; align-items: baseline; gap: 9px;
}
/* The mark is drawn by the border rather than shipped as a file, so it takes the caution
   colour in both modes and costs no request. */
.dl-note h2::before {
  content: "!"; flex: none;
  width: 18px; height: 18px; border-radius: 0;
  border: 1px solid var(--warn); align-self: center;
  font-family: var(--mono); font-size: .72rem; line-height: 16px; text-align: center;
}
.dl-note p { margin: 0 0 .5em; font-size: .9rem; line-height: 1.55; color: var(--text-2); max-width: 76ch; }
.dl-note p:last-child { margin-bottom: 0; }
/* The one sentence that is an instruction rather than an explanation. */
.dl-note .do { color: var(--text); }
.dl-note .do b { font-weight: 640; }

/* ── after the click ──
   Two short lists side by side: what happens next, and whether the machine can run it.
   Neither is long enough to deserve a screen of its own. */
.dl-after {
  display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 34px; align-items: start;
}
.dl-col h2 { font-size: 1.05rem; margin-bottom: 14px; }
.dl-col p, .dl-col li { color: var(--text-2); font-size: .92rem; line-height: 1.6; }

/* Numbered by hand: the counter can be given the brush colour and the mono face while
   the sentence beside it stays plain, which a list marker cannot do on its own. */
.dl-steps { list-style: none; counter-reset: step; margin: 0; padding: 0; }
.dl-steps li { counter-increment: step; position: relative; padding: 0 0 12px 34px; }
.dl-steps li::before {
  content: counter(step); position: absolute; left: 0; top: 1px;
  width: 22px; height: 22px; border-radius: 0;
  border: 1px solid var(--accent-dim); background: var(--accent-softer); color: var(--accent);
  font-family: var(--mono); font-size: .74rem; line-height: 20px; text-align: center;
}
.dl-steps li:last-child { padding-bottom: 0; }
.dl-steps b { color: var(--text); font-weight: 640; }

.dl-reqs { list-style: none; margin: 0; padding: 0; }
.dl-reqs li { position: relative; padding: 0 0 10px 20px; }
.dl-reqs li::before {
  content: ""; position: absolute; left: 0; top: .72em;
  width: 6px; height: 6px; border-radius: 0; background: var(--accent-dim);
}
.dl-reqs li:last-child { padding-bottom: 0; }

.dl-help { margin: 18px 0 0; }
.dl-help b { color: var(--text); font-weight: 640; }

/* The footnote nobody needs and one person a month wants: how the number is built. */
.dl-fine {
  margin: 30px 0 0; padding-top: 18px; border-top: 1px solid var(--line-soft);
  font-size: .8rem; color: var(--text-3);
}
.dl-fine code, .dl-fine b { font-family: var(--mono); font-weight: 400; color: var(--text-2); }

@media (max-width: 720px) {
  .dl-after { grid-template-columns: 1fr; gap: 28px; }
}
