<template> <div class="media-container"> <div class="media" :class="{ center: release.photos.length < 2 }" > <div v-if="release.trailer || release.teaser" class="trailer-container" > <Player v-if="release.trailer" :video="release.trailer" :poster="poster" class="item" @play="playing = true; paused = false;" @pause="playing = false; paused = true;" /> <Player v-else-if="release.teaser && /^video\//.test(release.teaser.mime)" :video="release.teaser" :poster="poster" :alt="release.title" :class="{ sfw: sfw && paused }" class="item" @play="playing = true; paused = false;" @pause="playing = false; paused = true;" /> <img v-else-if="release.teaser && /^image\//.test(release.teaser.mime)" :src="sfw ? `/img/${release.teaser.sfw.thumbnail}` : `/media/${release.teaser.path}`" :alt="release.title" loading="lazy" class="item trailer" > <a v-if="release.poster" v-tooltip="'View poster'" :href="`/media/${release.poster.path}`" :class="{ playing }" target="_blank" rel="noopener noreferrer" class="poster-link" ><Icon icon="image" /></a> <span v-if="sfw && !playing" class="warning" > <Icon icon="warning2" />NSFW </span> </div> <template v-if="release.covers && release.covers.length > 0"> <a v-for="cover in release.covers" :key="`cover-${cover.id}`" :href="`/media/${cover.path}`" target="_blank" rel="noopener noreferrer" > <img :src="`/media/${cover.thumbnail}`" :style="{ 'background-image': sfw ? `url(/media/${cover.sfw.lazy})` : `url(/media/${cover.lazy})` }" class="item cover" loading="lazy" @load="$emit('load', $event)" > </a> </template> <div v-for="photo in photos" :key="`media-${photo.id}`" class="item-container" > <a :href="`/media/${photo.path}`" :class="{ sfw }" class="item-link" target="_blank" rel="noopener noreferrer" > <img :src="sfw ? `/img/${photo.sfw.thumbnail}` : `/media/${photo.thumbnail}`" :style="{ 'background-image': sfw ? `url(/img/${photo.sfw.lazy})` : `url(/media/${photo.lazy})` }" :alt="`Photo ${photo.index + 1}`" loading="lazy" class="item" @load="$emit('load', $event)" > <span v-if="sfw" class="warning" > <Icon icon="warning2" />NSFW </span> </a> </div> </div> </div> </template> <script> import Player from '../video/player.vue'; function sfw() { return this.$store.state.ui.sfw; } function poster() { if (this.release.poster) { return this.sfw ? `/img/${this.release.poster.sfw.thumbnail}` : `/media/${this.release.poster.thumbnail}`; } if (this.release.covers?.length > 0) { return this.sfw ? `/img/${this.release.covers[0].sfw.path}` : `/media/${this.release.covers[0].path}`; } if (this.photos?.length > 0) { return this.sfw ? `/img/${this.photos[0].sfw.thumbnail}` : `/media/${this.photos[0].thumbnail}`; } return null; } function photos() { const clips = this.release.clips || []; const clipPostersById = clips.reduce((acc, clip) => ({ ...acc, [clip.poster.id]: clip.poster }), {}); const uniqueClipPosters = Array.from(new Set(clips.map(clip => clip.poster.id) || [])).map(posterId => clipPostersById[posterId]); const photosWithClipPosters = (this.release.photos || []).concat(uniqueClipPosters); if (this.release.trailer || (this.release.teaser && this.release.teaser.mime !== 'image/gif')) { // poster will be on trailer video return photosWithClipPosters; } if (this.release.poster) { // no trailer, add poster to photos return [this.release.poster].concat(photosWithClipPosters); } // no poster available return photosWithClipPosters; } export default { components: { Player, }, props: { release: { type: Object, default: null, }, expanded: { type: Boolean, default: false, }, test: { type: String, default: null, }, }, data() { return { player: null, playing: false, paused: false, }; }, computed: { photos, poster, sfw, }, }; </script> <style lang="scss" scoped> @import 'breakpoints'; .media-container { backdrop-filter: blur(1rem); } .media { flex-shrink: 0; white-space: nowrap; font-size: 0; } .media.center { width: 1200px; max-width: 100%; margin: 0 auto; } .poster-link { position: absolute; top: .5rem; right: .5rem; transition: opacity .1s ease; .icon { width: 1.5rem; height: 1.5rem; fill: var(--lighten-strong); filter: drop-shadow(0 0 1px var(--darken-weak)); } &.playing { opacity: 0; } &:hover { cursor: pointer; opacity: 1; .icon { fill: var(--text-light); } } } .item-container, .trailer-container { height: 100%; max-width: 100%; position: relative; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; .warning { display: none; width: 100%; height: 100%; flex-direction: column; align-items: center; justify-content: center; position: absolute; background: var(--darken-weak); color: var(--text-light); font-size: 1.2rem; font-weight: bold; text-shadow: 0 0 3px var(--darken-strong); pointer-events: none; animation: alert .5s ease infinite .1s; .icon { display: block; fill: var(--text-light); width: 3rem; height: 3rem; margin: 0 0 .25rem 0; filter: drop-shadow(0 0 3px var(--darken)); animation: alert .5s ease infinite .1s; } } &:hover .warning { display: inline-flex; } } .item { max-width: 100%; height: 18rem; box-shadow: 0 0 3px var(--shadow-weak); background-size: cover; } .trailer-container { width: 32rem; max-width: 100%; } .trailer { width: 100%; max-width: 32rem; max-height: 100%; object-fit: cover; &.sfw { filter: blur(2rem); } } @keyframes alert { 0% { color: var(--text-light); fill: var(--text-light); } 50% { color: var(--alert); fill: var(--alert); } } @media(max-width: $breakpoint-micro) { .media:not(.expanded) .item, .trailer-container { height: 56vw; /* 16:9 ratio for full-width video */ } } </style>