/* video-player.css */
/* Modal video player, opened from the play badge on a video still. */

/* A lightbox, not a dialog: the media card sits straight on the backdrop.
   `.modal-content` is not used at all here — see the template. */
#videoPlayerModal {
  /* --visible-viewport-height is published by panel-anchor.js when iPadOS
     strands the layout viewport after a fullscreen exit: `position: fixed`
     and `100vh` both resolve against that stranded viewport, so the flex
     centring below puts the whole player *below* the middle of the screen,
     and its bottom edge off it. This is the same variable swiper.css consumes
     for the photo itself, and it is unset (falling back to 100dvh) on every
     browser that does not have the bug. */
  height: var(--visible-viewport-height, 100dvh);
  background: rgba(0, 0, 0, 0.92);
  flex-direction: column;
  gap: 12px;
  /* Room for the close button, which floats over the backdrop's top-right. */
  padding: 24px;
  box-sizing: border-box;
}

/* Floats over the backdrop rather than sitting inside the frame: a control
   drawn on top of the picture is one more thing between the viewer and the
   video, and the corner of a shrink-wrapped frame moves with every clip's
   aspect ratio. */
.video-player-close {
  position: absolute;
  top: 12px;
  right: 16px;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  background: rgba(255, 255, 255, 0.1);
  border: none;
  border-radius: 50%;
  color: #fff;
  font: inherit;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s;
}

.video-player-close:hover,
.video-player-close:focus-visible {
  background: rgba(255, 255, 255, 0.22);
}

/* The media card. Shrink-wraps its <video>, so the border traces the picture
   at any aspect ratio instead of letterboxing it inside a fixed panel.

   No min-width/min-height: they were unconditional, so whenever the video was
   height-constrained to something narrower than the minimum — a portrait phone
   clip on a landscape screen, the most common shape in a phone library — the
   card was up to 160px wider than the picture, with all of the surplus on one
   side. There is no need for a floor: a <video> with nothing to size against
   has an intrinsic 300x150 of its own, which is what the overlay panels sit
   in until a poster or metadata arrives.

   Centring is what keeps any residual surplus symmetric rather than piling it
   against the left border. */
.video-player-frame {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  max-width: 100%;
  background: #000;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 12px;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.55);
}

/* `width`/`height: auto` with only maxima set is what makes the element size
   itself from the video's (or poster's) intrinsic aspect ratio, which is in
   turn what the frame shrink-wraps. Setting `width: 100%` here instead would
   pin the frame to the cap and put black bars back inside it. */
.video-player-element {
  display: block;
  width: auto;
  height: auto;
  /* Must not exceed what the frame can be: the frame's own max-width resolves
     against the modal's content box, which is 100vw minus its 24px padding on
     each side, plus the frame's 1px borders. A 94vw cap crossed that below
     800px of viewport, and since a flex item's automatic minimum size is its
     content size clamped by max-width, the video could not shrink to fit —
     the frame's overflow:hidden silently sliced ~24px off the right of every
     video on a phone. */
  max-width: min(calc(100vw - 52px), 1600px);
  /* The overlay's own 24px padding top and bottom, plus the caption row and
     its gap. Resolved against the visible viewport for the reason given
     above. */
  max-height: calc(var(--visible-viewport-height, 100dvh) - 104px);
  background: #000;
  /* The corners are rounded here rather than by an `overflow: hidden` on the
     frame. Clipping the video to its parent is the one way the native control
     bar can be taken away from the user entirely: the bar is drawn along the
     bottom edge of the video's own box, so any moment the frame is shorter
     than the video — the frame is a flex item with `overflow` set, whose
     automatic minimum height is therefore zero, so the column above it can
     squash it — the clip removes play/pause, the scrubber and fullscreen and
     leaves a picture that cannot be stopped. Rounding the replaced element
     itself clips its content the same way, and cannot cost anything. */
  border-radius: 11px;
}

/* The filename, as a caption on the backdrop rather than a title bar above
   the video — a row of chrome attached to the frame is most of what made the
   old player read as a dialog. */
.video-player-title {
  flex: none;
  max-width: min(94vw, 1600px);
  color: #bbb;
  font-size: 0.9rem;
  text-align: center;
  overflow-wrap: anywhere;
  /* Clamped to two lines because the video's max-height above subtracts a
     fixed allowance for this row. A long filename broken at arbitrary
     characters on a narrow screen would otherwise run to four or five lines
     and push the bottom of the picture back off the display — the exact
     problem the visible-viewport height is here to fix.

     The two lines are then *reserved*, at a line height fixed in pixels
     rather than left to the font. The allowance above is a constant, so the
     row it pays for has to be a constant too: 48px of padding, the 12px gap,
     this 36px and the frame's 2px of border come to 98 of the 104 budgeted.
     Sized in `em`, or left to whatever line height the platform's fallback
     font brings, two lines can exceed the allowance instead — and a column
     that overflows by even a few pixels squashes the frame, which is a flex
     item whose automatic minimum height is zero. Reserving the row also
     stops the picture jumping as the caption goes from one line to two. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-height: 18px;
  height: 36px;
  overflow: hidden;
}

.video-player-title:empty {
  display: none;
}

/* ===== Overlay panels (conversion progress, and failure) ===== */

/* Drawn over the frame, never in place of the video, so the poster stays
   visible behind the scrim and the frame keeps the clip's aspect ratio. */
.video-player-panel {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 24px;
  box-sizing: border-box;
  /* Matches the video's own rounding, which used to come from a clip on the
     frame. */
  border-radius: 11px;
  /* Heavy enough that the status text reads over an arbitrary frame — a
     bright or busy still shows straight through a lighter scrim — but not so
     heavy that the poster stops being visible. The point of overlaying rather
     than replacing the video is that the user keeps looking at the clip they
     asked for. */
  background: rgba(0, 0, 0, 0.82);
  color: #eee;
  text-align: center;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

.video-player-panel[hidden] {
  display: none;
}

/* Over a stream in progress: top edge, clear of the native control bar, and
   transparent to clicks so it never swallows one meant for the video. */
.video-player-stream-badge {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1;
  max-width: calc(100% - 24px);
  padding: 4px 12px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.7);
  color: #eee;
  font-size: 0.8rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}

.video-player-stream-badge[hidden] {
  display: none;
}

.video-player-panel-message {
  margin: 0;
  overflow-wrap: anywhere;
}

.video-player-panel-note {
  margin: 0;
  color: #aaa;
  font-size: 0.85rem;
  /* Reserves the line so the panel does not jump when the first percentage
     replaces an empty string. */
  min-height: 1em;
}

.video-player-bar {
  width: min(320px, 76%);
  height: 6px;
  border-radius: 999px;
  /* Opaque, not translucent: over a light frame a translucent trough is
     brighter than the fill and the bar reads backwards. */
  background: #2c2c2c;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
  overflow: hidden;
}

.video-player-bar-fill {
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: #6ea8fe;
  transition: width 0.3s ease;
}

/* A source whose duration ffmpeg could not determine gives no percentage to
   show, so the bar sweeps rather than lying about progress it does not have. */
.video-player-bar--indeterminate .video-player-bar-fill {
  width: 35%;
  animation: video-player-sweep 1.4s ease-in-out infinite;
}

@keyframes video-player-sweep {
  from {
    transform: translateX(-120%);
  }
  to {
    transform: translateX(340%);
  }
}

.video-player-download {
  display: inline-block;
  padding: 0.55em 1.2em;
  background: #3a3a3a;
  color: #fff;
  border-radius: 6px;
  text-decoration: none;
}

.video-player-download:hover,
.video-player-download:focus-visible {
  background: #4a4a4a;
}

.video-player-download[hidden] {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .video-player-close,
  .video-player-bar-fill {
    transition: none;
  }

  /* Still distinguishable from a real 100%: dimmed, and paired with the
     "unknown duration" wording in the panel above it. */
  .video-player-bar--indeterminate .video-player-bar-fill {
    width: 100%;
    animation: none;
    opacity: 0.45;
  }
}

/* ===== Playing in place =====

   The frame is pinned by video-player.js over the rectangle the poster's
   picture occupies, so pressing play swaps the still for the moving picture
   without anything changing size. No backdrop, and the overlay itself lets
   every pointer event through: the swiper's arrows, the panels and the rest of
   the page stay usable, and using them to move on is what closes the player.

   The z-index puts it above the slide (and its play badge, at 100) but below
   the UMAP window, the swiper's arrows and the side panels, which all sit over
   the photo too. */
#videoPlayerModal.video-player-overlay--anchored {
  background: transparent;
  padding: 0;
  pointer-events: none;
  z-index: 200;
}

/* The control and search panels float over the bottom corners of the photo,
   which is exactly where the video's native control bar is drawn, and the
   browser offers no way to move that bar. Dropping the panels beneath the
   video instead looked broken wherever it covered only part of one, so they
   fade out while a video plays in place and come back when it closes.

   `visibility` goes with the opacity so a hidden button can be neither
   clicked nor tabbed to; it flips only once the fade has finished. */
/* :where() keeps this at zero specificity. As a plain ID rule it outranked
   .hidden-fullscreen's own transition, and the panels stopped fading when
   the page went fullscreen. */
:where(#controlPanel, #searchPanel) {
  transition: opacity 0.25s ease;
}

body.video-playing-in-place #controlPanel,
body.video-playing-in-place #searchPanel {
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.25s ease,
    visibility 0s linear 0.25s;
}

/* The playing slide's own furniture. Its favourites star sits in the corner
   where the player puts its close button (half outside the picture on a
   letterboxed grid tile, so the two overlapped), and its play badge would
   show round the edges of a still that is now playing. */
.video-playing-slide > .bookmark-icon,
.video-playing-slide > .video-badge {
  visibility: hidden;
}

.video-player-overlay--anchored .video-player-frame {
  position: fixed;
  max-width: none;
  pointer-events: auto;
  /* Transparent rather than black: until the poster decodes, the identical
     still in the slide underneath shows through instead of a black flash. */
  background: transparent;
  border: none;
  box-shadow: none;
  /* The browser must not claim a horizontal drag for panning: it is how the
     user swipes to the next slide. */
  touch-action: pinch-zoom;
}

/* Sized by the frame, which has the picture's exact dimensions; `contain`
   only matters if the stream's aspect ratio differs from the still's. */
.video-player-overlay--anchored .video-player-element {
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: contain;
  background: transparent;
  border-radius: inherit;
}

/* The slide already names the file; a caption floating under the picture
   would sit on top of whatever is below it. */
.video-player-overlay--anchored .video-player-title {
  display: none;
}

.video-player-overlay--anchored .video-player-close {
  top: 8px;
  right: 8px;
  width: 32px;
  height: 32px;
  font-size: 1.3rem;
  background: rgba(0, 0, 0, 0.45);
}

.video-player-overlay--anchored .video-player-close:hover,
.video-player-overlay--anchored .video-player-close:focus-visible {
  background: rgba(0, 0, 0, 0.7);
}

/* A grid tile can be 75px across. Clip the panel's text rather than let it
   spill over the neighbouring tiles. */
.video-player-overlay--anchored .video-player-panel {
  padding: 8px;
  border-radius: inherit;
  overflow: hidden;
}
