<template> <div v-if="timeline" class="timeline" > <ul class="timeline-items nolist"> <li v-for="(chapter, index) in timeline" :key="`chapter-${chapter.id}`" :style="{ left: `${(chapter.time / duration) * 100}%`, bottom: `${(index % 3) * 1.5}rem`, }" :title="formatDuration(chapter.time)" class="timeline-item" > <a :href="`/tag/${chapter.tags[0].slug}`" class="link" >{{ chapter.tags[0]?.name || ' ' }}</a> <span class="timeline-timestamp">{{ formatDuration(chapter.time) }}</span> </li> </ul> </div> <ul v-else class="chapters nolist" > <li v-for="chapter in chapters" :key="`chapter-${chapter.id}`" class="chapter" > <img :src="getPath(chapter.poster, 'thumbnail')" :style="{ 'background-image': `url('${getPath(chapter.poster, 'lazy')}'` }" loading="lazy" class="chapter-poster" > <span class="chapter-details"> <span v-if="typeof chapter.time === 'number'" v-tooltip="'Time in video'" class="chapter-time" ><Icon icon="film3" /> {{ formatDuration(chapter.time) }}</span> <span v-if="chapter.duration" v-tooltip="'Duration'" class="chapter-duration" ><Icon icon="stopwatch" />{{ formatDuration(chapter.duration) }}</span> </span> <div class="chapter-info"> <h3 v-if="chapter.title" class="chapter-row chapter-title" :title="chapter.title" >{{ chapter.title }}</h3> <p v-if="chapter.description" class="chapter-row chapter-description" >{{ chapter.description }}</p> <ul class="chapter-tags chapter-row nolist"> <li v-for="tag in chapter.tags" :key="`chapter-tag-${tag.slug}`" class="chapter-tag" > <a :href="`/tag/${tag.slug}`" class="link" >{{ tag.name }}</a> </li> </ul> </div> </li> </ul> </template> <script setup> import { computed } from 'vue'; import getPath from '#/src/get-path.js'; import { formatDuration } from '#/utils/format.js'; const props = defineProps({ chapters: { type: Array, default: () => [], }, }); const lastChapter = props.chapters.at(-1); const duration = lastChapter.time + lastChapter.duration; const timeline = computed(() => { if (props.chapters.every((chapter) => chapter.time)) { return props.chapters.filter((chapter) => chapter.tags?.length > 0); } return null; }); </script> <style scoped> .chapters { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: .5rem; } .chapter { display: flex; flex-direction: column; background: var(--background); box-shadow: 0 0 3px var(--shadow-weak-30); border-radius: .25rem; margin: 0 0 .5rem 0; font-size: 0; overflow: hidden; } .chapter-poster { width: 100%; height: 10rem; object-fit: cover; object-position: center; } .chapter-details { height: 1.75rem; display: flex; justify-content: space-between; align-items: center; padding: 0 .5rem; border-radius: 0 0 .25rem .25rem; margin: 0 0 .5rem 0; color: var(--text-light); background: var(--grey-dark-40); font-size: .8rem; font-weight: bold; .icon { fill: var(--text-light); margin-right: .5rem; } } .chapter-duration, .chapter-time { display: flex; align-items: center; } .chapter-duration .icon { /* narrower icon */ margin: -.1rem .3rem 0 0; } .chapter-info { padding: 0 .5rem; font-size: 1rem; } .chapter-row { margin: 0 0 .5rem 0; } .chapter-title { padding: 0; font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .chapter-description { line-height: 1.5; } .chapter-tags { overflow: hidden; } .chapter-tag { margin: 0 .5rem .25rem 0; font-size: .75rem; color: var(--glass-strong-10); .link { color: inherit; text-decoration: none; } &:hover { color: var(--primary); } } .timeline { overflow: hidden; padding-right: 5rem; /* ensure last tag is cleared */ } .timeline-items { position: relative; height: 5rem; border-bottom: solid 1px var(--shadow-weak-30); } .timeline-item { position: absolute; bottom: -.25rem; padding: .1rem .5rem; border-radius: .6rem; background: var(--background); box-shadow: 0 0 3px var(--shadow-weak-30); font-size: .8rem; .link { color: var(--primary); font-weight: bold; } &:hover { z-index: 10; } &:before { content: ''; display: inline-block; width: 1px; height: 5rem; position: absolute; left: 0; margin: .3rem .5rem 0 0; border-left: dashed 1px var(--shadow-weak-30); } } .timeline-timestamp { color: var(--glass); margin-left: .25rem; } @media(--small-30) { .chapters { grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); } } </style>