/* The result viewer (shared/preview.js, OF1): the player inside a page's
   .player box (16:9, radius 12, ink-950, base.css) and the preview window
   over the page. A video keeps its own proportions inside the frame and
   plays muted on a loop under the player's own chips, an image fits the
   frame or shows at 100% with its own scroll, audio is a card. Where the
   file cannot show, the frame turns into a gray tile that says why:
   loading, expired, unavailable, not ready, a link that was not issued, or
   a file that would not play with its possible reasons and the ways out.
   Pages link this stylesheet after base.css. */
.pv-player { isolation: isolate; }
.pv-player.is-tile { background: var(--gray-100); }
.pv-player .pv-media { display: block; width: 100%; height: 100%; background: var(--ink-950); object-fit: contain; }
.pv-player:fullscreen { border-radius: 0; }

/* The player's chips, as the TaskDetail and Playground boards draw them
   (.vid-ctl, .vid-btn and .vid-time): at the video's foot on the left the
   32px round pause chip and the length on a 24px chip, on the right sound
   and full screen. Each chip is ink at .64 behind a blur, so its white
   glyph or text stays readable over bright snow, sky or cloud (about
   5.5:1 over white). The chips are scoped to the player, so the console's
   control rules (a phone's 44px floor for every button) never reach them:
   their 44px phone tap area is their ::before below. */
.pv-player.is-video .pv-media { cursor: pointer; }
.pv-player .pv-ctls { position: absolute; right: 12px; bottom: 12px; left: 12px; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: 8px; pointer-events: none; }
.pv-player .pv-group { display: flex; align-items: center; gap: 8px; pointer-events: auto; }
.pv-player .pv-ctl { position: relative; display: grid; flex: none; place-items: center; width: 32px; height: 32px; min-width: 0; min-height: 0; margin: 0; padding: 0; border: 0; border-radius: var(--r-full); background: rgba(16, 22, 31, .64); box-shadow: none; color: #fff; cursor: pointer; -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); transition: background-color var(--t-hover) var(--ease); }
.pv-player .pv-ctl:hover, .pv-player .pv-ctl:active { border: 0; background: rgba(16, 22, 31, .8); color: #fff; }
.pv-player .pv-ctl[hidden] { display: none; }
.pv-player .pv-ctl:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.pv-player .pv-len { flex: none; height: 24px; padding: 0 8px; border-radius: var(--r-6); background: rgba(16, 22, 31, .64); color: #fff; font: 500 12px/24px var(--font-mono); white-space: nowrap; font-variant-numeric: tabular-nums; -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }
.pv-player .pv-len[hidden] { display: none; }
.glyph.pv-glyph-play { width: 16px; height: 16px; clip-path: path("M4.5 2.6L13.2 8L4.5 13.4Z"); }
.glyph.pv-glyph-pause { width: 16px; height: 16px; clip-path: path("M4 3H6.75V13H4ZM9.25 3H12V13H9.25Z"); }
.glyph.pv-glyph-sound { width: 16px; height: 16px; clip-path: path("M1.5 6H4.25L8 2.75V13.25L4.25 10H1.5ZM10.71 4.17A5 5 0 0 1 10.71 11.83L9.81 10.76A3.6 3.6 0 0 0 9.81 5.24Z"); }
.glyph.pv-glyph-muted { width: 16px; height: 16px; clip-path: path("M1.5 6H4.25L8 2.75V13.25L4.25 10H1.5ZM9.85 6.52L10.77 5.6L12.25 7.08L13.73 5.6L14.65 6.52L13.17 8L14.65 9.48L13.73 10.4L12.25 8.92L10.77 10.4L9.85 9.48L11.33 8Z"); }
.glyph.pv-glyph-expand { width: 16px; height: 16px; clip-path: path("M2 2H6.5V3.5H3.5V6.5H2ZM9.5 2H14V6.5H12.5V3.5H9.5ZM2 9.5H3.5V12.5H6.5V14H2ZM12.5 9.5H14V14H9.5V12.5H12.5Z"); }
.glyph.pv-glyph-shrink { width: 16px; height: 16px; clip-path: path("M5 2H6.5V6.5H2V5H5ZM9.5 2H11V5H14V6.5H9.5ZM2 9.5H6.5V14H5V11H2ZM9.5 9.5H14V11H11V14H9.5Z"); }
.pv-toggle .pv-glyph-pause, .pv-player.is-playing .pv-toggle .pv-glyph-play { display: none; }
.pv-player.is-playing .pv-toggle .pv-glyph-pause { display: inline-block; }
.pv-mute .pv-glyph-muted, .pv-player.is-muted .pv-mute .pv-glyph-sound { display: none; }
.pv-player.is-muted .pv-mute .pv-glyph-muted { display: inline-block; }
.pv-full .pv-glyph-shrink, .pv-player.is-full .pv-full .pv-glyph-expand { display: none; }
.pv-player.is-full .pv-full .pv-glyph-shrink { display: inline-block; }

/* An image fits the frame; 原始大小 shows it at 100% and the frame scrolls
   from its top left corner. */
.pv-player.is-image { background: var(--gray-50); }
.pv-scroll { position: absolute; inset: 0; display: flex; overflow: auto; }
.pv-player .pv-image { flex: none; width: auto; height: auto; max-width: 100%; max-height: 100%; margin: auto; background: transparent; object-fit: contain; }
.pv-player.is-actual .pv-image { max-width: none; max-height: none; }
.pv-zoom { position: absolute; top: 12px; right: 12px; z-index: 1; box-shadow: var(--e1); }

/* Audio: a card with its icon, its name and the native controls. */
.pv-player.is-audio { aspect-ratio: auto; min-height: 0; background: var(--gray-25); box-shadow: inset 0 0 0 1px var(--line); }
.pv-audio { display: flex; align-items: center; gap: 16px; padding: 20px; }
.pv-audio-icon { display: grid; flex: none; place-items: center; width: 48px; height: 48px; border-radius: var(--r-12); background: var(--accent-tint); color: var(--violet-700); }
.pv-audio-body { display: flex; flex: 1 1 auto; flex-direction: column; gap: 8px; min-width: 0; }
.pv-audio-title { margin: 0; color: var(--text-1); font: 500 14px/20px var(--font-sans); }
.pv-audio audio { display: block; width: 100%; height: 40px; }

/* The states of the frame: an icon, a title and one line, centred. */
.pv-state { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; padding: 24px; overflow: auto; color: var(--text-2); text-align: center; }
.pv-player.is-audio .pv-state { position: static; min-height: 160px; }
.pv-state-icon { display: grid; flex: none; place-items: center; width: 40px; height: 40px; margin-bottom: 12px; border-radius: var(--r-8); background: var(--gray-0); color: var(--gray-700); box-shadow: 0 0 0 1px var(--line), var(--e1); }
.pv-state.is-danger .pv-state-icon { background: var(--danger-tint); color: var(--danger-fg); box-shadow: none; }
.pv-state-title { margin: 0; color: var(--text-1); font: 600 16px/24px var(--font-sans); }
.pv-state-text { max-width: 36em; margin: 0; color: var(--text-2); font-size: 13px; line-height: 20px; }
.pv-state-note { margin: 4px 0 0; color: var(--text-2); font-size: 13px; line-height: 20px; }
.pv-state-note:empty { display: none; }
.pv-state-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-top: 16px; }
.pv-reasons { margin: 4px 0 8px; padding: 0 0 0 18px; color: var(--text-2); font-size: 13px; line-height: 20px; text-align: left; }
.pv-reasons li + li { margin-top: 2px; }
/* A state with words of its own (a file that would not play, a link that
   was not issued, an expired, unavailable or unfinished result) is the
   sheet's compact tile, 216px at least, instead of the 16:9 frame: the
   room its words need and no more. */
.pv-player.is-grow { aspect-ratio: auto; min-height: 216px; }
.pv-player.is-grow .pv-state { position: static; min-height: 216px; }
/* Loading (R8): the frame keeps its look for 200ms; then the gray tile
   with the spinner shows and, once shown, stays 400ms at least: the
   pv-hold animation runs that time and shared/preview.js waits for its
   end before the file takes the frame. */
.pv-state.is-loading { gap: 12px; background: var(--gray-100); opacity: 0; animation: pv-hold 400ms linear 200ms forwards; }
.pv-player.is-audio .pv-state.is-loading { background: none; }
@keyframes pv-hold { from, to { opacity: 1; } }
.pv-spinner { width: 24px; height: 24px; border: 2px solid var(--gray-200); border-top-color: var(--violet-600); border-radius: 50%; animation: pv-spin .8s linear infinite; }
@keyframes pv-spin { to { transform: rotate(360deg); } }

/* The preview window: the 960 dialog with the task's short ID beside its
   title, the player, the file's facts and until when it can be
   downloaded, then a line for what an action did and the actions, 下载视频
   the one primary. The player keeps 16:9 inside the screen's height; a
   compact tile takes the window's width. */
.pv-overlay { z-index: var(--z-overlay); }
.pv-window { gap: 16px; }
.pv-window:focus { outline: none; }
.pv-head { display: flex; align-items: center; gap: 12px; min-width: 0; margin: -4px -8px 0 0; }
.pv-heading { display: flex; flex: 1 1 auto; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; min-width: 0; }
.pv-title { flex: none; }
.pv-sub { min-width: 0; overflow: hidden; color: var(--text-2); font: 400 13px/20px var(--font-mono); text-overflow: ellipsis; white-space: nowrap; }
.pv-stage { flex: none; width: min(100%, calc((100vh - 260px) * 16 / 9)); min-width: min(100%, 320px); margin: 0 auto; }
.pv-stage.is-grow { width: 100%; }
.pv-meta { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 16px; min-height: 20px; color: var(--text-2); font-size: 13px; line-height: 20px; font-variant-numeric: tabular-nums; }
.pv-until { margin-left: auto; white-space: nowrap; }
.pv-meta:has(> .pv-spec:empty):has(> .pv-until:empty) { display: none; }
.pv-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding-top: 16px; border-top: 1px solid var(--line-soft); }
.pv-note { flex: 1 1 200px; min-width: 0; margin: 0; color: var(--text-2); font-size: 13px; line-height: 20px; }
.pv-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; margin-left: auto; }
.pv-actions > [hidden] { display: none; }

/* Phones: the window is a sheet from the bottom of the screen (base.css),
   the player takes its width and the actions share the last row; the
   player's chips stay 32px and round, answer taps over 44px and keep 12px
   apart, so no two tap areas cover each other. */
@media (max-width: 767px) {
  /* base.css keeps .dialog-xl at 960px on phones; the window takes the
     screen's width as every phone dialog does. */
  .pv-window.dialog { width: 100%; max-width: 100%; max-height: calc(100vh - 24px); gap: 12px; padding: 16px 16px calc(16px + env(safe-area-inset-bottom)); }
  .pv-head { margin-right: -4px; }
  .pv-stage { width: 100%; min-width: 0; }
  .pv-zoom { top: 8px; right: 8px; }
  .pv-audio { padding: 16px; }
  .pv-actions { width: 100%; }
  /* The actions share the row by their words and wrap where a language's
     words do not fit (English puts 下载视频 on a row of its own). */
  .pv-actions > .btn { flex: 1 1 auto; min-width: 0; }
  .pv-state-actions .btn { min-height: 44px; }
  .pv-player .pv-group { gap: 12px; }
  .pv-player .pv-ctl::before { content: ""; position: absolute; inset: -6px; }
}
@media (prefers-reduced-motion: reduce) {
  .pv-spinner { animation: none; }
}
