<template> <div :id="`scene-${release.id}`" :class="{ new: release.isNew }" class="tile" > <Details :release="release" class="details-compact" /> <div class="tile-body"> <span class="poster"> <a :href="`/scene/${release.id}/${release.slug || ''}`" target="_blank" rel="noopener noreferrer" class="link" > <img v-if="release.poster" :src="getPath(release.poster, 'thumbnail')" :style="{ 'background-image': getBgPath(release.poster, 'lazy') }" :alt="release.title" :width="release.poster.thumbnailWidth" :height="release.poster.thumbnailHeight" class="thumbnail" loading="lazy" > <img v-else-if="release.photos && release.photos.length > 0" :src="getPath(release.photos[0], 'thumbnail')" :style="{ 'background-image': getBgPath(release.photos[0], 'lazy') } " :alt="release.title" :width="release.photos[0].thumbnailWidth" :height="release.photos[0].thumbnailHeight" class="thumbnail" loading="lazy" > <div v-else :title="release.title" class="thumbnail unavailable" ><Icon icon="blocked" />No thumbnail available</div> <Icon v-show="(!stash || stash.primary) && favorited" icon="heart7" class="stash stashed" @click.prevent.native="unstashScene" /> <Icon v-show="(!stash || stash.primary) && favorited === false" icon="heart8" class="stash unstashed" @click.prevent.native="stashScene" /> <Icon v-show="stash && !stash.primary" icon="cross2" class="stash unstash" @click.prevent.native="unstashScene" /> </a> </span> <div class="info"> <Details :release="release" class="details-wide" /> <a :href="`/scene/${release.id}/${release.slug || ''}`" target="_blank" rel="noopener noreferrer" class="row link" > <h3 v-if="release.title" v-tooltip.bottom="release.title" :title="release.title" class="title" > {{ release.title }}<template v-if="release.movies?.[0]?.title && /^scene \d+$/i.test(release.title)"><span class="title-composed"> from </span>{{ release.movies[0].title }}</template> </h3> <h3 v-else-if="release.actors.length > 0" class="title title-composed" >{{ release.actors[0].name }} for {{ release.entity.name }}</h3> <h3 v-else class="title title-empty" >{{ release.entity.name }}</h3> </a> <span class="row"> <ul class="actors nolist" :title="release.actors.map(actor => actor.name).join(', ')" > <li v-for="actor in release.actors" :key="actor.id" class="actor" > <RouterLink :to="{ name: 'actor', params: { actorId: actor.id, actorSlug: actor.slug } }" :class="{ [actor.gender]: !!actor.gender }" class="actor-link" >{{ actor.name }}</RouterLink> </li> </ul> </span> <div class="labels"> <RouterLink v-if="release.shootId && release.studio" :to="`/studio/${release.studio.slug}`" :title="release.studio && release.studio.name" class="shoot nolink" >{{ release.shootId }}</RouterLink> <span v-else-if="release.shootId" :title="release.studio && release.studio.name" class="shoot nolink" >{{ release.shootId }}</span> <ul v-if="release.tags?.length > 0" :title="release.tags.map(tag => tag.name).join(', ')" class="tags nolist" > <li v-for="tag in release.tags" :key="`tag-${tag.slug}`" class="tag" > <RouterLink :to="`/tag/${tag.slug}`" class="tag-link" >{{ tag.name }}</RouterLink> </li> </ul> </div> </div> </div> </div> </template> <script> import Details from './tile-details.vue'; async function stashScene() { this.favorited = true; try { await this.$store.dispatch('stashScene', { sceneId: this.release.id, stashId: this.$store.getters.favorites.id, }); this.$emit('stash', true); } catch (error) { this.favorited = false; } } async function unstashScene() { if (!this.stash || this.stash.primary) { this.favorited = false; } try { await this.$store.dispatch('unstashScene', { sceneId: this.release.id, stashId: this.stash?.id || this.$store.getters.favorites.id, }); this.$emit('stash', false); } catch (error) { this.favorited = true; } } export default { components: { Details, }, props: { release: { type: Object, default: null, }, stash: { type: Object, default: null, }, }, emits: ['stash'], data() { return { favorited: this.release.isFavorited, }; }, methods: { stashScene, unstashScene, }, }; </script> <style lang="scss" scoped> @import 'breakpoints'; .tile { background: var(--background); position: relative; display: flex; flex-direction: column; box-sizing: border-box; overflow: hidden; height: 100%; box-shadow: 0 0 3px var(--darken-weak); &.new .poster::after { content: 'new'; position: absolute; display: flex; align-items: center; justify-content: center; bottom: 0; right: .5rem; width: 2rem; box-sizing: border-box; padding: .1rem 0 .05rem 0; border-radius: .25rem .25rem 0 0; background: var(--info); color: var(--text-light); font-size: .7rem; font-weight: bold; line-height: 1; box-shadow: 0 0 3px var(--shadow); } &:hover .unstashed, &:hover .unstash { fill: var(--lighten); } } .tile-body { display: flex; flex-direction: column; } .poster { position: relative; } .covers { background: var(--profile); display: flex; .cover { width: 50%; } } .thumbnail { width: 100%; height: 14rem; display: flex; justify-content: center; align-items: center; object-fit: cover; background-position: center; background-size: cover; background-color: var(--shadow-hint); color: var(--shadow); text-shadow: 1px 1px 0 var(--highlight); .icon { display: none; width: 2rem; height: 2rem; fill: var(--shadow-hint); } } .stash { width: 1.5rem; height: 1.5rem; position: absolute; top: 0; right: 0; padding: .25rem .25rem .5rem .5rem; filter: drop-shadow(0 0 2px var(--darken)); fill: var(--lighten-weak); &:hover.unstashed, &.stashed { fill: var(--primary); filter: drop-shadow(0 0 2px var(--darken-weak)); } &:hover.unstash { fill: var(--text-light); filter: drop-shadow(0 0 2px var(--darken-weak)); } } .row { max-width: 100%; display: flex; justify-content: space-between; align-items: center; box-sizing: border-box; padding: 0 .5rem; } .info { display: flex; flex-direction: column; align-items: flex-start; flex-grow: 1; overflow: hidden; } .link { text-decoration: none; } .title { margin: 0; color: var(--text); font-size: .9rem; line-height: 1.5; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .title-composed, .title-empty { color: var(--shadow); } .actors { height: 1.5rem; word-wrap: break-word; overflow: hidden; line-height: 1.5rem; margin: 0 0 .25rem 0; } .actor:not(:last-of-type)::after { content: ","; margin: 0 .25rem 0 0; } .actor-link { color: var(--text); text-decoration: none; font-size: .9rem; &:hover { color: var(--primary); } } .labels { padding: 0 .5rem 1.25rem .5rem; max-height: .5rem; overflow-y: hidden; font-size: 0; line-height: 2; } .shoot { display: inline; padding: .25rem .5rem; background: var(--primary); color: var(--text-light); font-size: 0.75rem; font-weight: bold; box-shadow: inset 0 0 3px var(--shadow-weak); } .tags { display: inline; word-wrap: break-word; } .tag { margin: 0 0 1rem 0; &:not(:first-child) .tag-link { border-left: none; } } .tag-link { background: var(--shadow-touch); color: var(--shadow); display: inline-block; padding: .25rem .5rem; margin: 0 1px 0 0; font-size: .75rem; font-weight: bold; text-decoration: none; line-height: 1; &:hover { color: var(--primary); } } .details-wide { margin: 0 0 .4rem 0; } .details-compact { display: none; } @media(max-width: $breakpoint) { .thumbnail { height: 11rem; } } @media(max-width: $breakpoint-micro) { .tile-body { flex-direction: row; } .poster { margin: 0; } .thumbnail { width: 9rem; height: 6rem; font-size: 0; box-shadow: 0 0 3px var(--shadow-weak); .icon { display: block; } } .info { padding: .5rem .25rem 0 .25rem; } .row { margin: 0 0 .15rem 0; } .title, .actor-link { font-size: .9rem; } .details-wide { display: none; } .details-compact { display: flex; } .shoot { display: none; } .tile.new .poster::after { top: 0; right: .25rem; bottom: auto; border-radius: 0 0 .25rem .25rem; padding: .05rem 0 .1rem 0; } .stash { left: 0; padding: .25rem .5rem .5rem .25rem; } } </style>