<template> <div class="page"> <div class="content"> <div class="banner-container" :style="{ 'background-image': scene.poster.isS3 ? `url('https://cdndev.traxxx.me/${scene.poster.thumbnail}')` : `url('/media/${scene.poster.thumbnail}')` }" > <div class="banner"> <div class="poster-container"> <a :href="scene.poster.isS3 ? `https://cdndev.traxxx.me/${scene.poster.path}` : `/media/${scene.poster.path}`" target="_blank" class="poster-link" > <img v-if="scene.poster" :src="scene.poster.isS3 ? `https://cdndev.traxxx.me/${scene.poster.thumbnail}` : `/media/${scene.poster.thumbnail}`" :style="{ 'background-image': scene.poster.isS3 ? `url(https://cdndev.traxxx.me/${scene.poster.lazy})` : `url(/media/${scene.poster.lazy})` }" :width="scene.poster.width" :height="scene.poster.height" class="poster" > </a> </div> <div v-if="scene.photos.length > 0" class="album" > <div v-for="photo in scene.photos" :key="`photo-${photo.id}`" class="photo-container" > <a :href="photo.isS3 ? `https://cdndev.traxxx.me/${photo.path}` : `/media/${photo.path}`" target="_blank" class="photo-link" > <img :src="photo.isS3 ? `https://cdndev.traxxx.me/${photo.thumbnail}` : `/media/${photo.thumbnail}`" :style="{ 'background-image': photo.isS3 ? `url(https://cdndev.traxxx.me/${photo.lazy})` : `url(/media/${photo.lazy})` }" :width="photo.width" :height="photo.height" class="photo" > </a> </div> </div> </div> </div> <div class="meta"> <div class="entity"> <Link v-if="scene.channel" :href="`/${scene.channel.type}/${scene.channel.slug}`" class="channel-link entity-link" > <img v-if="scene.channel.hasLogo" :src="scene.channel.isIndependent || !scene.network ? `/logos/${scene.channel.slug}/network.png` : `/logos/${scene.network.slug}/${scene.channel.slug}.png`" class="channel-logo entity-logo" > </Link> <template v-if="!scene.channel.isIndependent && scene.network"> by <Link :href="`/${scene.network.type}/${scene.network.slug}`" class="network-link entity-link" > <img v-if="scene.network.hasLogo" :src="`/logos/${scene.network.slug}/network.png`" class="network-logo entity-logo" > </Link> </template> </div> <time :datetime="scene.effectiveDate.toISOString()" class="date" >{{ formatDate(scene.effectiveDate, 'MMMM d, y') }}</time> </div> <div class="header"> <h2 :title="scene.title" class="title" >{{ scene.title }}</h2> <div class="actions"> <div class="bookmarks"> <Icon icon="folder-heart" /> <Icon v-show="favorited" icon="heart7" class="heart favorited" @click.native.stop="unstash" /> <Icon v-show="!favorited && user" icon="heart8" class="heart" @click.native.stop="stash" /> </div> <div class="view"> <button v-if="scene.photos.length > 0" class="button view nolink" >View photos</button> <Link v-if="scene.url" :href="scene.url" target="_blank" class="button watch nolink" >Watch scene</Link> </div> </div> </div> <div class="info"> <ul class="actors nolist"> <li v-for="actor in scene.actors" :key="`actor-${actor.id}`" class="actor" > <ActorTile :actor="actor" /> </li> </ul> <ul class="tags nolist"> <li v-for="tag in scene.tags" :key="`tag-${tag.id}`" > <Link :href="`/tag/${tag.slug}`" class="tag nolink" >{{ tag.name }}</Link> </li> </ul> <div class="section"> <h3 class="heading">Description</h3> <p v-if="scene.description" class="description" >{{ scene.description }}</p> </div> <div class="section details"> <div v-if="scene.duration" class="detail" > <h3 class="heading">Duration</h3> {{ formatDuration(scene.duration) }} </div> <div v-if="scene.directors.length > 0" class="detail" > <h3 class="heading">Director</h3> {{ scene.directors.map((director) => director.name).join(', ') }} </div> </div> <div class="section details"> <div class="detail"> <h3 class="heading">Added</h3> <span class="added-date">{{ formatDate(scene.createdAt, 'yyyy-MM-dd hh:mm') }}</span> <span class="added-batch">batch #{{ scene.createdBatchId }}</span> </div> <div v-if="scene.comment" class="detail" > <h3 class="heading">Comment</h3> {{ scene.comment }} </div> </div> </div> </div> </div> </template> <script setup> import { ref, inject } from 'vue'; import { post, del } from '#/src/api.js'; import { formatDate, formatDuration } from '#/utils/format.js'; import Icon from '../../components/icon/icon.vue'; import ActorTile from '../../components/actors/tile.vue'; const { pageProps, user } = inject('pageContext'); const { scene } = pageProps; const favorited = ref(scene.stashes.some((sceneStash) => sceneStash.primary)); async function stash() { try { favorited.value = true; await post(`/stashes/${user.primaryStash.id}/scenes`, { sceneId: scene.id }); } catch (error) { favorited.value = false; } } async function unstash() { try { favorited.value = false; await del(`/stashes/${user.primaryStash.id}/scenes/${scene.id}`); } catch (error) { console.error(error); favorited.value = true; } } </script> <style scoped> .page { display: flex; justify-content: center; background: var(--background-base-10); } .content { width: 100%; max-width: 1200px; display: flex; flex-direction: column; } .banner-container { background-position: center; background-size: cover; margin-top: .5rem; } .banner { max-height: 18rem; display: flex; font-size: 0; backdrop-filter: blur(1rem); } .poster { height: auto; max-height: 18rem; width: auto; max-width: 100%; border-radius: .25rem .25rem 0 0; } .poster, .photo { object-fit: cover; background-size: cover; background-position: center; box-shadow: 0 0 3px var(--shadow-weak-10); } .album { height: 100%; flex-grow: 1; display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); gap: .5rem; padding: 0 .5rem .5rem .5rem; overflow-y: auto; } .photo-container { width: 100%; display: flex; justify-content: center; align-items: center; } .photo { width: 100%; height: 100%; border-radius: .25rem; } .meta { display: flex; justify-content: space-between; align-items: center; background: var(--grey-dark-40); border-radius: 0 0 .25rem .25rem; color: var(--text-light); } .entity { display: flex; align-items: center; font-weight: bold; color: var(--highlight); } .entity-link { padding: .5rem 1rem; } .entity-logo { height: 1.5rem; } .date { padding: 1rem; font-weight: bold; } .info, .header { border: solid 1px var(--shadow-weak-40); border-top: none; border-bottom: none; } .header { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1rem 1.5rem 1rem; } .title { margin: 0 .5rem 0 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .actions { display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; .icon { width: 1.5rem; height: 1.5rem; fill: var(--shadow); } .button { flex-shrink: 0; padding: .75rem; margin-right: .5rem; } } .bookmarks { display: flex; margin-right: .75rem; .icon { padding: .5rem .5rem; } .icon.heart:hover, .icon.heart.favorited { cursor: pointer; fill: var(--primary); } } .view { display: flex; } .watch { background: var(--primary); color: var(--text-light); } .info { padding: 0 1rem; } .actors, .tags { margin-bottom: 1rem; } .actors { display: grid; flex-grow: 1; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); gap: .25rem; } .tag { padding: .5rem; border-radius: .25rem; margin: 0 .25rem .25rem 0; background: var(--background); box-shadow: 0 0 3px var(--shadow-weak-30); &:hover { color: var(--primary); box-shadow: 0 0 3px var(--shadow-weak-20); cursor: pointer; } } .section { margin-bottom: 1rem; } .heading { color: var(--primary); margin: 0 0 .5rem 0; font-size: .9rem; } .details { display: flex; gap: 1rem; } .description { font-size: 1rem; line-height: 1.5; text-align: justify; margin: 0; } .added-batch { color: var(--shadow); } @media (--compact) { .header { flex-direction: column-reverse; } .actions { width: 100%; justify-content: space-between; margin-bottom: 1.5rem; } .view { flex-direction: row-reverse; } .title { width: 100%; margin-left: 1rem; } .meta { border-radius: 0; } } </style>