mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-18 19:11:42 +08:00
1352 lines
40 KiB
Vue
1352 lines
40 KiB
Vue
<script setup lang="ts">
|
||
import { copyToClipboard } from '@/@core/utils/navigator'
|
||
import api from '@/api'
|
||
import type { SubscribeDownloadFileInfo, SubscribeEpisodeInfo, SubscribeLibraryFileInfo, SubscrbieInfo } from '@/api/types'
|
||
import { useGlobalSettingsStore } from '@/stores'
|
||
import { getDisplayImageUrl } from '@/utils/imageUtils'
|
||
import { useDisplay } from 'vuetify'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { useToast } from 'vue-toastification'
|
||
|
||
type SubscribeFileTab = 'download' | 'library'
|
||
type EpisodeStatus = 'library' | 'download' | 'missing'
|
||
|
||
interface SubscribeEpisodeGroup {
|
||
episodeNumber: number
|
||
episodeLabel: string
|
||
title: string
|
||
description?: string
|
||
backdrop?: string
|
||
download: SubscribeDownloadFileInfo[]
|
||
library: SubscribeLibraryFileInfo[]
|
||
status: EpisodeStatus
|
||
}
|
||
|
||
interface FileStatItem {
|
||
key: string
|
||
label: string
|
||
value: number
|
||
total: number
|
||
icon: string
|
||
color: 'primary' | 'info' | 'success' | 'warning'
|
||
}
|
||
|
||
// i18n
|
||
const { t } = useI18n()
|
||
|
||
// 显示器宽度
|
||
const display = useDisplay()
|
||
|
||
// 全局设置
|
||
const globalSettingsStore = useGlobalSettingsStore()
|
||
const globalSettings = globalSettingsStore.globalSettings
|
||
|
||
// 提示框
|
||
const $toast = useToast()
|
||
|
||
// 输入参数
|
||
const props = defineProps({
|
||
subid: Number,
|
||
})
|
||
|
||
// 定义触发的自定义事件
|
||
const emit = defineEmits(['close'])
|
||
|
||
// 当前文件类型页签
|
||
const activeTab = ref<SubscribeFileTab>('download')
|
||
|
||
// 当前选中的集数
|
||
const selectedEpisodeNumber = ref<number>()
|
||
|
||
// 订阅文件信息
|
||
const subScribeInfo = ref<SubscrbieInfo>()
|
||
|
||
// 是否加载中
|
||
const loading = ref(false)
|
||
|
||
/**
|
||
* 调用 API 查询订阅文件信息。
|
||
*/
|
||
async function loadSubscribeFilesInfo() {
|
||
try {
|
||
loading.value = true
|
||
subScribeInfo.value = await api.get(`subscribe/files/${props.subid}`)
|
||
} catch (e) {
|
||
console.log(e)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 将图片地址转换为当前全局缓存设置下可展示的地址。
|
||
*/
|
||
function resolveImageUrl(url?: string) {
|
||
return getDisplayImageUrl(url || '', globalSettings.GLOBAL_IMAGE_CACHE)
|
||
}
|
||
|
||
/**
|
||
* 根据订阅媒体类型生成集数展示文案。
|
||
*/
|
||
function formatEpisodeLabel(episodeNumber: number) {
|
||
if (subScribeInfo.value?.subscribe?.type === '电影') return t('dialog.subscribeFiles.movieEpisode')
|
||
return `E${String(episodeNumber).padStart(2, '0')}`
|
||
}
|
||
|
||
/**
|
||
* 根据单集文件命中情况判断当前集状态。
|
||
*/
|
||
function resolveEpisodeStatus(download: SubscribeDownloadFileInfo[], library: SubscribeLibraryFileInfo[]): EpisodeStatus {
|
||
if (library.length) return 'library'
|
||
if (download.length) return 'download'
|
||
return 'missing'
|
||
}
|
||
|
||
/**
|
||
* 返回集状态对应的本地化展示文案。
|
||
*/
|
||
function getEpisodeStatusText(status: EpisodeStatus) {
|
||
const statusMap: Record<EpisodeStatus, string> = {
|
||
library: t('dialog.subscribeFiles.statusInLibrary'),
|
||
download: t('dialog.subscribeFiles.statusDownloaded'),
|
||
missing: t('dialog.subscribeFiles.statusMissing'),
|
||
}
|
||
|
||
return statusMap[status]
|
||
}
|
||
|
||
/**
|
||
* 返回集状态对应的 Vuetify 主题色。
|
||
*/
|
||
function getEpisodeStatusColor(status: EpisodeStatus) {
|
||
const colorMap: Record<EpisodeStatus, 'success' | 'info' | 'warning'> = {
|
||
library: 'success',
|
||
download: 'info',
|
||
missing: 'warning',
|
||
}
|
||
|
||
return colorMap[status]
|
||
}
|
||
|
||
/**
|
||
* 返回集状态对应的图标。
|
||
*/
|
||
function getEpisodeStatusIcon(status: EpisodeStatus) {
|
||
const iconMap: Record<EpisodeStatus, string> = {
|
||
library: 'mdi-check-circle-outline',
|
||
download: 'mdi-download-circle-outline',
|
||
missing: 'mdi-clock-outline',
|
||
}
|
||
|
||
return iconMap[status]
|
||
}
|
||
|
||
/**
|
||
* 返回订阅状态对应的本地化展示文案。
|
||
*/
|
||
function getSubscribeStateText(state?: string) {
|
||
const stateMap: Record<string, string> = {
|
||
R: t('dialog.subscribeFiles.subscribeRunning'),
|
||
P: t('dialog.subscribeFiles.subscribePending'),
|
||
S: t('dialog.subscribeFiles.subscribePaused'),
|
||
N: t('dialog.subscribeFiles.subscribeNew'),
|
||
}
|
||
|
||
return stateMap[state || ''] || t('common.unknown')
|
||
}
|
||
|
||
/**
|
||
* 返回订阅状态对应的 Vuetify 主题色。
|
||
*/
|
||
function getSubscribeStateColor(state?: string) {
|
||
if (state === 'R') return 'success'
|
||
if (state === 'S') return 'warning'
|
||
if (state === 'P') return 'info'
|
||
return 'primary'
|
||
}
|
||
|
||
/**
|
||
* 计算数值在总量中的百分比,保证进度条不会溢出。
|
||
*/
|
||
function calcPercent(value: number, total: number) {
|
||
if (!total) return 0
|
||
return Math.min(Math.max((value / total) * 100, 0), 100)
|
||
}
|
||
|
||
/**
|
||
* 从种子标题或文件路径中提取分辨率标签。
|
||
*/
|
||
function resolveResolutionLabel(file?: SubscribeDownloadFileInfo | SubscribeLibraryFileInfo) {
|
||
const text = 'torrent_title' in (file || {}) ? `${(file as SubscribeDownloadFileInfo).torrent_title || ''} ${file?.file_path || ''}` : file?.file_path || ''
|
||
const matched = text.match(/(?:2160|1080|720|480)p|4k|8k/i)
|
||
return matched?.[0]?.toUpperCase()
|
||
}
|
||
|
||
/**
|
||
* 复制文件路径到剪贴板并提示结果。
|
||
*/
|
||
async function copyPath(path?: string) {
|
||
if (!path) return
|
||
|
||
try {
|
||
const success = await copyToClipboard(path)
|
||
if (success) $toast.success(t('dialog.subscribeFiles.copySuccess'))
|
||
else $toast.error(t('dialog.subscribeFiles.copyFailed'))
|
||
} catch (e) {
|
||
console.log(e)
|
||
$toast.error(t('dialog.subscribeFiles.copyFailed'))
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 切换当前选中的集数。
|
||
*/
|
||
function selectEpisode(episodeNumber: number) {
|
||
selectedEpisodeNumber.value = episodeNumber
|
||
}
|
||
|
||
// 订阅信息
|
||
const subscribe = computed(() => subScribeInfo.value?.subscribe)
|
||
|
||
// 所有集的展示数据
|
||
const episodeGroups = computed<SubscribeEpisodeGroup[]>(() => {
|
||
const episodes = subScribeInfo.value?.episodes ?? {}
|
||
|
||
return Object.keys(episodes)
|
||
.map(key => Number(key))
|
||
.sort((left, right) => left - right)
|
||
.map(episodeNumber => {
|
||
const item = episodes[episodeNumber] as SubscribeEpisodeInfo | undefined
|
||
const download = item?.download ?? []
|
||
const library = item?.library ?? []
|
||
|
||
return {
|
||
episodeNumber,
|
||
episodeLabel: formatEpisodeLabel(episodeNumber),
|
||
title: item?.title || formatEpisodeLabel(episodeNumber),
|
||
description: item?.description,
|
||
backdrop: item?.backdrop,
|
||
download,
|
||
library,
|
||
status: resolveEpisodeStatus(download, library),
|
||
}
|
||
})
|
||
})
|
||
|
||
// 总集数
|
||
const totalCount = computed(() => {
|
||
const subscribeTotal = subscribe.value?.total_episode ?? 0
|
||
if (subscribe.value?.type === '电影') return Math.max(episodeGroups.value.length, 1)
|
||
return Math.max(subscribeTotal, episodeGroups.value.length)
|
||
})
|
||
|
||
// 已下载集数
|
||
const downloadedCount = computed(() => episodeGroups.value.filter(item => item.download.length).length)
|
||
|
||
// 已入库集数
|
||
const libraryCount = computed(() => episodeGroups.value.filter(item => item.library.length).length)
|
||
|
||
// 缺失集数
|
||
const missingCount = computed(() => Math.max(totalCount.value - libraryCount.value, 0))
|
||
|
||
// 顶部统计卡片
|
||
const statItems = computed<FileStatItem[]>(() => [
|
||
{
|
||
key: 'total',
|
||
label: t('dialog.subscribeFiles.totalEpisodes'),
|
||
value: totalCount.value,
|
||
total: totalCount.value,
|
||
icon: 'mdi-view-grid-outline',
|
||
color: 'primary',
|
||
},
|
||
{
|
||
key: 'download',
|
||
label: t('dialog.subscribeFiles.downloadedCount'),
|
||
value: downloadedCount.value,
|
||
total: totalCount.value,
|
||
icon: 'mdi-cloud-download-outline',
|
||
color: 'info',
|
||
},
|
||
{
|
||
key: 'library',
|
||
label: t('dialog.subscribeFiles.libraryCount'),
|
||
value: libraryCount.value,
|
||
total: totalCount.value,
|
||
icon: 'mdi-server-network',
|
||
color: 'success',
|
||
},
|
||
])
|
||
|
||
// 当前选中集
|
||
const selectedEpisode = computed(() => {
|
||
return episodeGroups.value.find(item => item.episodeNumber === selectedEpisodeNumber.value) ?? episodeGroups.value[0]
|
||
})
|
||
|
||
// 当前选中集的当前页签文件列表
|
||
const selectedFiles = computed(() => {
|
||
if (!selectedEpisode.value) return []
|
||
return activeTab.value === 'download' ? selectedEpisode.value.download : selectedEpisode.value.library
|
||
})
|
||
|
||
// 顶部主背景图
|
||
const heroBackdropUrl = computed(() => {
|
||
return resolveImageUrl(selectedEpisode.value?.backdrop || subscribe.value?.backdrop || subscribe.value?.poster)
|
||
})
|
||
|
||
// 顶部海报图
|
||
const posterUrl = computed(() => resolveImageUrl(subscribe.value?.poster))
|
||
|
||
// 顶部背景图内联变量
|
||
const heroStyle = computed(() => ({
|
||
'--subscribe-files-backdrop': heroBackdropUrl.value ? `url("${heroBackdropUrl.value}")` : 'none',
|
||
}))
|
||
|
||
// 当前页签标题
|
||
const activeSectionTitle = computed(() => {
|
||
return activeTab.value === 'download' ? t('dialog.subscribeFiles.downloadTab') : t('dialog.subscribeFiles.libraryTab')
|
||
})
|
||
|
||
// 移动端逐集文件列表
|
||
const mobileEpisodeGroups = computed(() => {
|
||
return episodeGroups.value.map(item => ({
|
||
...item,
|
||
activeFiles: activeTab.value === 'download' ? item.download : item.library,
|
||
}))
|
||
})
|
||
|
||
watch(
|
||
episodeGroups,
|
||
episodes => {
|
||
if (!episodes.length) {
|
||
selectedEpisodeNumber.value = undefined
|
||
return
|
||
}
|
||
|
||
if (!episodes.some(item => item.episodeNumber === selectedEpisodeNumber.value)) {
|
||
selectedEpisodeNumber.value = episodes[0].episodeNumber
|
||
}
|
||
},
|
||
{ immediate: true },
|
||
)
|
||
|
||
onBeforeMount(() => {
|
||
loadSubscribeFilesInfo()
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<VDialog
|
||
scrollable
|
||
max-width="74rem"
|
||
:fullscreen="!display.mdAndUp.value"
|
||
content-class="subscribe-files-overlay"
|
||
>
|
||
<VCard class="subscribe-files-dialog">
|
||
<VBtn
|
||
class="subscribe-files-dialog__close"
|
||
icon="mdi-close"
|
||
variant="text"
|
||
size="small"
|
||
:aria-label="t('common.close')"
|
||
@click="emit('close')"
|
||
/>
|
||
|
||
<LoadingBanner v-if="loading" />
|
||
|
||
<VCardText v-else class="subscribe-files-dialog__body">
|
||
<div v-if="subScribeInfo?.subscribe" class="subscribe-files-shell">
|
||
<section class="subscribe-files-hero" :style="heroStyle">
|
||
<div class="subscribe-files-hero__shade" />
|
||
<div class="subscribe-files-hero__content">
|
||
<div class="subscribe-files-poster-card">
|
||
<VImg :src="posterUrl" cover class="subscribe-files-poster-card__image">
|
||
<template #placeholder>
|
||
<VSkeletonLoader class="w-100 h-100" />
|
||
</template>
|
||
</VImg>
|
||
<div class="subscribe-files-poster-card__caption">
|
||
<span>{{ subscribe?.name }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="subscribe-files-hero__meta">
|
||
<div class="subscribe-files-hero__eyebrow">
|
||
<VIcon icon="mdi-folder-play-outline" size="18" />
|
||
<span>{{ t('dialog.subscribeFiles.title') }}</span>
|
||
</div>
|
||
<h2 class="subscribe-files-hero__title">{{ subscribe?.name }}</h2>
|
||
<div class="subscribe-files-hero__chips">
|
||
<VChip v-if="subscribe?.season" color="primary" variant="flat" size="small">
|
||
{{ t('dialog.subscribeFiles.season', { number: subscribe.season }) }}
|
||
</VChip>
|
||
<VChip v-if="subscribe?.year" variant="tonal" size="small">{{ subscribe.year }}</VChip>
|
||
<VChip
|
||
:color="getSubscribeStateColor(subscribe?.state)"
|
||
variant="flat"
|
||
size="small"
|
||
prepend-icon="mdi-check-circle"
|
||
>
|
||
{{ getSubscribeStateText(subscribe?.state) }}
|
||
</VChip>
|
||
</div>
|
||
<p class="subscribe-files-hero__description">
|
||
{{ selectedEpisode?.description || subscribe?.description || t('dialog.subscribeFiles.noOverview') }}
|
||
</p>
|
||
</div>
|
||
|
||
<div class="subscribe-files-stats">
|
||
<div v-for="item in statItems" :key="item.key" class="subscribe-files-stat-card">
|
||
<div class="subscribe-files-stat-card__icon">
|
||
<VIcon :icon="item.icon" :color="item.color" size="22" />
|
||
</div>
|
||
<div class="subscribe-files-stat-card__content">
|
||
<div class="subscribe-files-stat-card__label">{{ item.label }}</div>
|
||
<div class="subscribe-files-stat-card__value">{{ item.value }}</div>
|
||
</div>
|
||
<VProgressLinear
|
||
:model-value="calcPercent(item.value, item.total)"
|
||
:color="item.color"
|
||
height="3"
|
||
rounded
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="subscribe-files-content">
|
||
<aside v-if="display.mdAndUp.value" class="subscribe-files-episode-rail">
|
||
<div class="subscribe-files-episode-rail__header">
|
||
<div>
|
||
<span>{{ t('dialog.subscribeFiles.episodeList') }}</span>
|
||
<strong>{{ t('dialog.subscribeFiles.episodeTotal', { count: totalCount }) }}</strong>
|
||
</div>
|
||
<VChip size="small" color="warning" variant="tonal">
|
||
{{ t('dialog.subscribeFiles.missingCount', { count: missingCount }) }}
|
||
</VChip>
|
||
</div>
|
||
|
||
<div class="subscribe-files-episode-list">
|
||
<button
|
||
v-for="episode in episodeGroups"
|
||
:key="episode.episodeNumber"
|
||
class="subscribe-files-episode-item"
|
||
:class="{ 'subscribe-files-episode-item--active': selectedEpisode?.episodeNumber === episode.episodeNumber }"
|
||
type="button"
|
||
@click="selectEpisode(episode.episodeNumber)"
|
||
>
|
||
<span class="subscribe-files-episode-item__label">{{ episode.episodeLabel }}</span>
|
||
<span class="subscribe-files-episode-item__title">{{ episode.title }}</span>
|
||
<VChip :color="getEpisodeStatusColor(episode.status)" variant="tonal" size="x-small">
|
||
{{ getEpisodeStatusText(episode.status) }}
|
||
</VChip>
|
||
<VIcon
|
||
class="subscribe-files-episode-item__icon"
|
||
:icon="getEpisodeStatusIcon(episode.status)"
|
||
:color="getEpisodeStatusColor(episode.status)"
|
||
size="18"
|
||
/>
|
||
</button>
|
||
</div>
|
||
</aside>
|
||
|
||
<main class="subscribe-files-main">
|
||
<div class="subscribe-files-tabs">
|
||
<VBtnToggle
|
||
v-model="activeTab"
|
||
mandatory
|
||
divided
|
||
class="subscribe-files-tab-group"
|
||
selected-class="subscribe-files-tab-group__button--active"
|
||
>
|
||
<VBtn value="download" class="subscribe-files-tab-group__button">
|
||
<VIcon size="20" start icon="mdi-download" />
|
||
{{ t('dialog.subscribeFiles.downloadTab') }}
|
||
</VBtn>
|
||
<VBtn value="library" class="subscribe-files-tab-group__button">
|
||
<VIcon size="20" start icon="mdi-filmstrip-box-multiple" />
|
||
{{ t('dialog.subscribeFiles.libraryTab') }}
|
||
</VBtn>
|
||
</VBtnToggle>
|
||
</div>
|
||
|
||
<div v-if="display.mdAndUp.value" class="subscribe-files-detail">
|
||
<div class="subscribe-files-detail__header">
|
||
<div>
|
||
<div class="subscribe-files-detail__title">
|
||
<span>{{ selectedEpisode?.episodeLabel }}</span>
|
||
<strong>{{ selectedEpisode?.title }}</strong>
|
||
</div>
|
||
<div class="subscribe-files-detail__subtitle">
|
||
{{ activeSectionTitle }}
|
||
</div>
|
||
</div>
|
||
<VChip
|
||
v-if="selectedEpisode"
|
||
:color="getEpisodeStatusColor(selectedEpisode.status)"
|
||
variant="tonal"
|
||
size="small"
|
||
>
|
||
{{ getEpisodeStatusText(selectedEpisode.status) }}
|
||
</VChip>
|
||
</div>
|
||
|
||
<transition name="fade-slide" mode="out-in">
|
||
<div :key="activeTab" class="subscribe-files-file-list">
|
||
<template v-if="selectedFiles.length">
|
||
<article
|
||
v-for="(file, index) in selectedFiles"
|
||
:key="`${activeTab}-${file.file_path || index}`"
|
||
class="subscribe-files-file-card"
|
||
>
|
||
<div class="subscribe-files-file-card__media">
|
||
<VIcon
|
||
:icon="activeTab === 'download' ? 'mdi-play' : 'mdi-server-network'"
|
||
:color="activeTab === 'download' ? 'info' : 'success'"
|
||
size="20"
|
||
/>
|
||
</div>
|
||
<div class="subscribe-files-file-card__content">
|
||
<div class="subscribe-files-file-card__topline">
|
||
<VChip v-if="resolveResolutionLabel(file)" color="primary" variant="tonal" size="x-small">
|
||
{{ resolveResolutionLabel(file) }}
|
||
</VChip>
|
||
<VChip
|
||
v-if="activeTab === 'download' && (file as SubscribeDownloadFileInfo).site_name"
|
||
color="primary"
|
||
variant="tonal"
|
||
size="x-small"
|
||
>
|
||
{{ (file as SubscribeDownloadFileInfo).site_name }}
|
||
</VChip>
|
||
<VChip
|
||
v-if="activeTab === 'library' && (file as SubscribeLibraryFileInfo).storage"
|
||
color="success"
|
||
variant="tonal"
|
||
size="x-small"
|
||
>
|
||
{{ (file as SubscribeLibraryFileInfo).storage }}
|
||
</VChip>
|
||
<VChip
|
||
:color="activeTab === 'download' ? 'info' : 'success'"
|
||
variant="flat"
|
||
size="x-small"
|
||
>
|
||
{{ activeTab === 'download' ? t('dialog.subscribeFiles.statusDownloaded') : t('dialog.subscribeFiles.statusInLibrary') }}
|
||
</VChip>
|
||
</div>
|
||
<h3 class="subscribe-files-file-card__title">
|
||
{{ activeTab === 'download' ? ((file as SubscribeDownloadFileInfo).torrent_title || t('dialog.subscribeFiles.unknownTorrent')) : activeSectionTitle }}
|
||
</h3>
|
||
<div v-if="activeTab === 'download'" class="subscribe-files-file-card__meta">
|
||
<span v-if="(file as SubscribeDownloadFileInfo).downloader">
|
||
{{ t('dialog.subscribeFiles.downloader') }}:{{ (file as SubscribeDownloadFileInfo).downloader }}
|
||
</span>
|
||
<span v-if="(file as SubscribeDownloadFileInfo).hash">
|
||
Hash:{{ (file as SubscribeDownloadFileInfo).hash }}
|
||
</span>
|
||
</div>
|
||
<div class="subscribe-files-path-block">
|
||
<div class="subscribe-files-path-block__label">
|
||
<VIcon icon="mdi-folder-outline" size="16" />
|
||
{{ t('dialog.subscribeFiles.filePath') }}
|
||
</div>
|
||
<code>{{ file.file_path || t('dialog.subscribeFiles.noPath') }}</code>
|
||
<VBtn
|
||
icon="mdi-content-copy"
|
||
variant="tonal"
|
||
size="small"
|
||
:disabled="!file.file_path"
|
||
:aria-label="t('dialog.subscribeFiles.copyPath')"
|
||
@click="copyPath(file.file_path)"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
</template>
|
||
|
||
<div v-else class="subscribe-files-empty">
|
||
<VIcon icon="mdi-folder-search-outline" size="34" />
|
||
<div>{{ t('dialog.subscribeFiles.noTabFiles', { tab: activeSectionTitle }) }}</div>
|
||
</div>
|
||
</div>
|
||
</transition>
|
||
</div>
|
||
|
||
<div v-else class="subscribe-files-mobile-list">
|
||
<article
|
||
v-for="episode in mobileEpisodeGroups"
|
||
:key="episode.episodeNumber"
|
||
class="subscribe-files-mobile-card"
|
||
:class="`subscribe-files-mobile-card--${episode.status}`"
|
||
>
|
||
<div class="subscribe-files-mobile-card__header">
|
||
<div class="subscribe-files-mobile-card__episode">{{ episode.episodeLabel }}</div>
|
||
<div class="subscribe-files-mobile-card__title">
|
||
<strong>{{ episode.title }}</strong>
|
||
<span>{{ activeSectionTitle }}</span>
|
||
</div>
|
||
<VChip :color="getEpisodeStatusColor(episode.status)" variant="tonal" size="small">
|
||
{{ getEpisodeStatusText(episode.status) }}
|
||
</VChip>
|
||
</div>
|
||
|
||
<div v-if="episode.activeFiles.length" class="subscribe-files-mobile-card__files">
|
||
<div
|
||
v-for="(file, index) in episode.activeFiles"
|
||
:key="`${episode.episodeNumber}-${activeTab}-${file.file_path || index}`"
|
||
class="subscribe-files-mobile-file"
|
||
>
|
||
<div class="subscribe-files-mobile-file__chips">
|
||
<VChip v-if="resolveResolutionLabel(file)" color="primary" variant="tonal" size="x-small">
|
||
{{ resolveResolutionLabel(file) }}
|
||
</VChip>
|
||
<VChip
|
||
v-if="activeTab === 'download' && (file as SubscribeDownloadFileInfo).site_name"
|
||
color="primary"
|
||
variant="tonal"
|
||
size="x-small"
|
||
>
|
||
{{ (file as SubscribeDownloadFileInfo).site_name }}
|
||
</VChip>
|
||
<VChip
|
||
v-if="activeTab === 'library' && (file as SubscribeLibraryFileInfo).storage"
|
||
color="success"
|
||
variant="tonal"
|
||
size="x-small"
|
||
>
|
||
{{ (file as SubscribeLibraryFileInfo).storage }}
|
||
</VChip>
|
||
</div>
|
||
<div v-if="activeTab === 'download'" class="subscribe-files-mobile-file__title">
|
||
{{ (file as SubscribeDownloadFileInfo).torrent_title || t('dialog.subscribeFiles.unknownTorrent') }}
|
||
</div>
|
||
<div class="subscribe-files-path-block subscribe-files-path-block--mobile">
|
||
<code>{{ file.file_path || t('dialog.subscribeFiles.noPath') }}</code>
|
||
<VBtn
|
||
icon="mdi-content-copy"
|
||
variant="tonal"
|
||
size="small"
|
||
:disabled="!file.file_path"
|
||
:aria-label="t('dialog.subscribeFiles.copyPath')"
|
||
@click="copyPath(file.file_path)"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="subscribe-files-mobile-card__empty">
|
||
{{ t('dialog.subscribeFiles.noTabFiles', { tab: activeSectionTitle }) }}
|
||
</div>
|
||
</article>
|
||
</div>
|
||
</main>
|
||
</section>
|
||
</div>
|
||
|
||
<div v-else class="subscribe-files-empty subscribe-files-empty--standalone">
|
||
<VIcon icon="mdi-folder-alert-outline" size="40" />
|
||
<div>{{ t('dialog.subscribeFiles.noData') }}</div>
|
||
</div>
|
||
</VCardText>
|
||
</VCard>
|
||
</VDialog>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.subscribe-files-dialog {
|
||
overflow: hidden;
|
||
border: 1px solid rgba(var(--v-theme-on-surface), var(--sfd-border-opacity));
|
||
backdrop-filter: blur(var(--sfd-blur)) saturate(1.18);
|
||
background:
|
||
linear-gradient(145deg, rgba(var(--v-theme-primary), var(--sfd-accent-opacity)), transparent 42%),
|
||
rgba(var(--v-theme-surface), var(--sfd-dialog-opacity)) !important;
|
||
color: rgb(var(--v-theme-on-surface));
|
||
|
||
--sfd-accent-opacity: 0.1;
|
||
--sfd-blur: 18px;
|
||
--sfd-border-opacity: 0.14;
|
||
--sfd-dialog-opacity: 0.9;
|
||
--sfd-hero-end-opacity: 0.88;
|
||
--sfd-hero-mid-opacity: 0.62;
|
||
--sfd-hero-shade-opacity: 0.92;
|
||
--sfd-hero-start-opacity: 0.98;
|
||
--sfd-panel-opacity: 0.12;
|
||
--sfd-panel-strong-opacity: 0.18;
|
||
--sfd-code-opacity: 0.16;
|
||
--sfd-muted-opacity: 0.7;
|
||
}
|
||
|
||
.subscribe-files-dialog__close {
|
||
position: absolute;
|
||
z-index: 8;
|
||
border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
||
background: rgba(var(--v-theme-surface), 0.22);
|
||
inset-block-start: 1rem;
|
||
inset-inline-end: 1rem;
|
||
}
|
||
|
||
.subscribe-files-dialog__body {
|
||
overflow: hidden;
|
||
padding: 0 !important;
|
||
}
|
||
|
||
.subscribe-files-shell {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-block-size: min(86vh, 56rem);
|
||
}
|
||
|
||
.subscribe-files-hero {
|
||
position: relative;
|
||
overflow: hidden;
|
||
min-block-size: 21rem;
|
||
background:
|
||
linear-gradient(
|
||
90deg,
|
||
rgba(var(--v-theme-surface), var(--sfd-hero-start-opacity)),
|
||
rgba(var(--v-theme-surface), var(--sfd-hero-mid-opacity)) 48%,
|
||
rgba(var(--v-theme-surface), var(--sfd-hero-end-opacity))
|
||
),
|
||
var(--subscribe-files-backdrop) center / cover;
|
||
}
|
||
|
||
.subscribe-files-hero__shade {
|
||
position: absolute;
|
||
background:
|
||
radial-gradient(circle at 24% 8%, rgba(var(--v-theme-primary), 0.24), transparent 34%),
|
||
linear-gradient(180deg, transparent, rgba(var(--v-theme-surface), var(--sfd-hero-shade-opacity)) 94%);
|
||
inset: 0;
|
||
}
|
||
|
||
.subscribe-files-hero__content {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: grid;
|
||
align-items: end;
|
||
gap: 2rem;
|
||
grid-template-columns: 15rem minmax(0, 1fr) 14rem;
|
||
padding: 3.5rem 2.25rem 2rem;
|
||
}
|
||
|
||
.subscribe-files-poster-card {
|
||
position: relative;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(var(--v-theme-on-surface), 0.14);
|
||
border-radius: calc(var(--app-surface-radius) + 0.25rem);
|
||
aspect-ratio: 2 / 3;
|
||
background: rgba(var(--v-theme-surface), var(--sfd-panel-strong-opacity));
|
||
}
|
||
|
||
.subscribe-files-poster-card__image {
|
||
block-size: 100%;
|
||
}
|
||
|
||
.subscribe-files-poster-card__caption {
|
||
position: absolute;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
padding: 1.25rem 1rem 1rem;
|
||
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.72));
|
||
color: white;
|
||
font-size: 1.35rem;
|
||
font-weight: 700;
|
||
inset-block-end: 0;
|
||
inset-inline: 0;
|
||
line-height: 1.25;
|
||
text-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
|
||
}
|
||
|
||
.subscribe-files-hero__meta {
|
||
min-inline-size: 0;
|
||
}
|
||
|
||
.subscribe-files-hero__eyebrow {
|
||
display: flex;
|
||
align-items: center;
|
||
color: rgba(var(--v-theme-on-surface), var(--sfd-muted-opacity));
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
gap: 0.35rem;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
|
||
.subscribe-files-hero__title {
|
||
overflow: hidden;
|
||
margin-block: 0.75rem 0.5rem;
|
||
font-size: clamp(2.1rem, 4vw, 3.25rem);
|
||
font-weight: 800;
|
||
letter-spacing: -0.04em;
|
||
line-height: 1.08;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.subscribe-files-hero__chips {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.subscribe-files-hero__description {
|
||
display: -webkit-box;
|
||
overflow: hidden;
|
||
color: rgba(var(--v-theme-on-surface), 0.76);
|
||
font-size: 0.95rem;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 3;
|
||
line-clamp: 3;
|
||
line-height: 1.75;
|
||
margin-block: 1rem 0;
|
||
max-inline-size: 42rem;
|
||
}
|
||
|
||
.subscribe-files-stats {
|
||
display: grid;
|
||
gap: 0.8rem;
|
||
}
|
||
|
||
.subscribe-files-stat-card {
|
||
display: grid;
|
||
align-items: center;
|
||
padding: 0.9rem;
|
||
border: 1px solid rgba(var(--v-theme-on-surface), 0.1);
|
||
border-radius: var(--app-surface-radius);
|
||
background: rgba(var(--v-theme-surface), var(--sfd-panel-opacity));
|
||
gap: 0.65rem;
|
||
grid-template-columns: auto 1fr;
|
||
}
|
||
|
||
.subscribe-files-stat-card > .v-progress-linear {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.subscribe-files-stat-card__content {
|
||
min-inline-size: 0;
|
||
}
|
||
|
||
.subscribe-files-stat-card__icon {
|
||
display: grid;
|
||
border-radius: 50%;
|
||
background: rgba(var(--v-theme-on-surface), 0.06);
|
||
block-size: 2.4rem;
|
||
inline-size: 2.4rem;
|
||
place-items: center;
|
||
}
|
||
|
||
.subscribe-files-stat-card__label {
|
||
color: rgba(var(--v-theme-on-surface), var(--sfd-muted-opacity));
|
||
font-size: 0.78rem;
|
||
}
|
||
|
||
.subscribe-files-stat-card__value {
|
||
font-size: 1.45rem;
|
||
font-weight: 800;
|
||
line-height: 1.15;
|
||
}
|
||
|
||
.subscribe-files-content {
|
||
display: grid;
|
||
flex: 1 1 auto;
|
||
gap: 1rem;
|
||
grid-template-columns: 19rem minmax(0, 1fr);
|
||
min-block-size: 0;
|
||
padding: 0 1.25rem 1.25rem;
|
||
}
|
||
|
||
.subscribe-files-episode-rail,
|
||
.subscribe-files-main {
|
||
overflow: hidden;
|
||
border: 1px solid rgba(var(--v-theme-on-surface), 0.1);
|
||
border-radius: var(--app-surface-radius);
|
||
background: rgba(var(--v-theme-surface), var(--sfd-panel-opacity));
|
||
}
|
||
|
||
.subscribe-files-episode-rail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-block-size: 0;
|
||
}
|
||
|
||
.subscribe-files-episode-rail__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 1rem;
|
||
border-block-end: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.subscribe-files-episode-rail__header div {
|
||
display: grid;
|
||
gap: 0.15rem;
|
||
}
|
||
|
||
.subscribe-files-episode-rail__header span {
|
||
color: rgba(var(--v-theme-on-surface), var(--sfd-muted-opacity));
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.subscribe-files-episode-list {
|
||
display: flex;
|
||
overflow: auto;
|
||
flex: 1 1 auto;
|
||
flex-direction: column;
|
||
gap: 0.35rem;
|
||
min-block-size: 0;
|
||
padding: 0.75rem;
|
||
}
|
||
|
||
.subscribe-files-episode-item {
|
||
display: grid;
|
||
align-items: center;
|
||
padding: 0.75rem;
|
||
border: 1px solid transparent;
|
||
border-radius: calc(var(--app-surface-radius) - 0.25rem);
|
||
background: transparent;
|
||
color: rgb(var(--v-theme-on-surface));
|
||
column-gap: 0.65rem;
|
||
cursor: pointer;
|
||
grid-template-columns: 2.75rem minmax(0, 1fr) auto auto;
|
||
text-align: start;
|
||
transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
|
||
}
|
||
|
||
.subscribe-files-episode-item:hover,
|
||
.subscribe-files-episode-item--active {
|
||
border-color: rgba(var(--v-theme-primary), 0.38);
|
||
background: rgba(var(--v-theme-primary), 0.12);
|
||
}
|
||
|
||
.subscribe-files-episode-item--active {
|
||
transform: translateX(2px);
|
||
}
|
||
|
||
.subscribe-files-episode-item__label {
|
||
display: grid;
|
||
border-radius: 0.75rem;
|
||
background: rgba(var(--v-theme-primary), 0.18);
|
||
block-size: 2.4rem;
|
||
color: rgb(var(--v-theme-primary));
|
||
font-weight: 800;
|
||
inline-size: 2.4rem;
|
||
place-items: center;
|
||
}
|
||
|
||
.subscribe-files-episode-item__title {
|
||
overflow: hidden;
|
||
font-weight: 600;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.subscribe-files-episode-item__icon {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.subscribe-files-main {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-block-size: 0;
|
||
}
|
||
|
||
.subscribe-files-tabs {
|
||
padding: 1rem 1rem 0;
|
||
}
|
||
|
||
.subscribe-files-tab-group {
|
||
display: grid;
|
||
overflow: hidden;
|
||
inline-size: min(28rem, 100%);
|
||
border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
||
border-radius: var(--app-control-radius);
|
||
background: rgba(var(--v-theme-surface), var(--sfd-panel-opacity));
|
||
gap: 0.35rem;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
padding: 0.25rem;
|
||
}
|
||
|
||
.subscribe-files-tab-group :deep(.v-btn) {
|
||
min-inline-size: 0;
|
||
}
|
||
|
||
.subscribe-files-tab-group :deep(.v-btn__content) {
|
||
flex-wrap: nowrap;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.subscribe-files-tab-group :deep(.v-icon) {
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.subscribe-files-tab-group__button {
|
||
border-radius: calc(var(--app-control-radius) - 0.15rem) !important;
|
||
color: rgba(var(--v-theme-on-surface), 0.72) !important;
|
||
font-weight: 700;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.subscribe-files-tab-group__button--active {
|
||
background: linear-gradient(135deg, rgba(var(--v-theme-primary), 0.92), rgba(var(--v-theme-primary), 0.68)) !important;
|
||
color: rgb(var(--v-theme-on-primary)) !important;
|
||
}
|
||
|
||
.subscribe-files-detail {
|
||
display: flex;
|
||
flex: 1 1 auto;
|
||
flex-direction: column;
|
||
min-block-size: 0;
|
||
padding: 1rem;
|
||
}
|
||
|
||
.subscribe-files-detail__header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
margin-block-end: 1rem;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.subscribe-files-detail__title {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.subscribe-files-detail__title span {
|
||
color: rgb(var(--v-theme-primary));
|
||
font-weight: 800;
|
||
}
|
||
|
||
.subscribe-files-detail__title strong {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.subscribe-files-detail__subtitle {
|
||
color: rgba(var(--v-theme-on-surface), var(--sfd-muted-opacity));
|
||
font-size: 0.85rem;
|
||
margin-block-start: 0.2rem;
|
||
}
|
||
|
||
.subscribe-files-file-list {
|
||
display: flex;
|
||
overflow: auto;
|
||
flex: 1 1 auto;
|
||
flex-direction: column;
|
||
gap: 0.8rem;
|
||
min-block-size: 0;
|
||
padding-block-end: 0.25rem;
|
||
}
|
||
|
||
.subscribe-files-file-card {
|
||
display: grid;
|
||
padding: 0.9rem;
|
||
border: 1px solid rgba(var(--v-theme-on-surface), 0.1);
|
||
border-radius: var(--app-surface-radius);
|
||
background: rgba(var(--v-theme-surface), var(--sfd-panel-opacity));
|
||
gap: 0.8rem;
|
||
grid-template-columns: auto minmax(0, 1fr);
|
||
}
|
||
|
||
.subscribe-files-file-card__media {
|
||
display: grid;
|
||
border-radius: 0.85rem;
|
||
background: rgba(var(--v-theme-on-surface), 0.06);
|
||
block-size: 2.5rem;
|
||
inline-size: 2.5rem;
|
||
place-items: center;
|
||
}
|
||
|
||
.subscribe-files-file-card__content {
|
||
min-inline-size: 0;
|
||
}
|
||
|
||
.subscribe-files-file-card__topline,
|
||
.subscribe-files-mobile-file__chips {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.35rem;
|
||
}
|
||
|
||
.subscribe-files-file-card__title {
|
||
overflow: hidden;
|
||
margin-block: 0.6rem 0.35rem;
|
||
font-size: 0.96rem;
|
||
font-weight: 700;
|
||
line-height: 1.5;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.subscribe-files-file-card__meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
color: rgba(var(--v-theme-on-surface), var(--sfd-muted-opacity));
|
||
font-size: 0.78rem;
|
||
gap: 0.4rem 0.8rem;
|
||
margin-block-end: 0.65rem;
|
||
}
|
||
|
||
.subscribe-files-path-block {
|
||
display: grid;
|
||
align-items: center;
|
||
padding: 0.65rem;
|
||
border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
||
border-radius: calc(var(--app-surface-radius) - 0.25rem);
|
||
background: rgba(var(--v-theme-on-surface), var(--sfd-code-opacity));
|
||
gap: 0.55rem;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
}
|
||
|
||
.subscribe-files-path-block__label {
|
||
display: flex;
|
||
align-items: center;
|
||
color: rgba(var(--v-theme-on-surface), var(--sfd-muted-opacity));
|
||
font-size: 0.78rem;
|
||
gap: 0.25rem;
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.subscribe-files-path-block code {
|
||
overflow: hidden;
|
||
color: rgba(var(--v-theme-on-surface), 0.88);
|
||
font-family: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
|
||
font-size: 0.8rem;
|
||
line-height: 1.55;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.subscribe-files-empty {
|
||
display: grid;
|
||
align-content: center;
|
||
justify-items: center;
|
||
min-block-size: 12rem;
|
||
border: 1px dashed rgba(var(--v-theme-on-surface), 0.16);
|
||
border-radius: var(--app-surface-radius);
|
||
color: rgba(var(--v-theme-on-surface), var(--sfd-muted-opacity));
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.subscribe-files-empty--standalone {
|
||
min-block-size: 24rem;
|
||
margin: 1.5rem;
|
||
}
|
||
|
||
.subscribe-files-mobile-list {
|
||
display: flex;
|
||
overflow: auto;
|
||
flex: 1 1 auto;
|
||
flex-direction: column;
|
||
gap: 0.85rem;
|
||
padding: 1rem;
|
||
}
|
||
|
||
.subscribe-files-mobile-card {
|
||
overflow: hidden;
|
||
border: 1px solid rgba(var(--v-theme-on-surface), 0.1);
|
||
border-radius: var(--app-surface-radius);
|
||
background: rgba(var(--v-theme-surface), var(--sfd-panel-opacity));
|
||
}
|
||
|
||
.subscribe-files-mobile-card__header {
|
||
display: grid;
|
||
align-items: center;
|
||
padding: 0.85rem;
|
||
gap: 0.65rem;
|
||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||
}
|
||
|
||
.subscribe-files-mobile-card__episode {
|
||
display: grid;
|
||
border-radius: 0.8rem;
|
||
background: rgba(var(--v-theme-primary), 0.18);
|
||
block-size: 3rem;
|
||
color: rgb(var(--v-theme-primary));
|
||
font-size: 1.05rem;
|
||
font-weight: 800;
|
||
inline-size: 3rem;
|
||
place-items: center;
|
||
}
|
||
|
||
.subscribe-files-mobile-card__title {
|
||
display: grid;
|
||
min-inline-size: 0;
|
||
}
|
||
|
||
.subscribe-files-mobile-card__title strong {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.subscribe-files-mobile-card__title span,
|
||
.subscribe-files-mobile-card__empty {
|
||
color: rgba(var(--v-theme-on-surface), var(--sfd-muted-opacity));
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.subscribe-files-mobile-card__files {
|
||
display: grid;
|
||
border-block-start: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
||
gap: 0.75rem;
|
||
padding: 0.85rem;
|
||
}
|
||
|
||
.subscribe-files-mobile-file {
|
||
display: grid;
|
||
gap: 0.55rem;
|
||
}
|
||
|
||
.subscribe-files-mobile-file__title {
|
||
overflow: hidden;
|
||
font-size: 0.86rem;
|
||
font-weight: 600;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.subscribe-files-path-block--mobile {
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
}
|
||
|
||
.subscribe-files-mobile-card__empty {
|
||
padding: 0 0.85rem 0.85rem;
|
||
}
|
||
|
||
.fade-slide-enter-active,
|
||
.fade-slide-leave-active {
|
||
transition: opacity 0.16s ease, transform 0.16s ease;
|
||
}
|
||
|
||
.fade-slide-enter-from,
|
||
.fade-slide-leave-to {
|
||
opacity: 0;
|
||
transform: translateY(0.25rem);
|
||
}
|
||
|
||
.subscribe-files-dialog.v-theme--light {
|
||
--sfd-accent-opacity: 0.08;
|
||
--sfd-border-opacity: 0.11;
|
||
--sfd-dialog-opacity: 0.96;
|
||
--sfd-panel-opacity: 0.62;
|
||
--sfd-panel-strong-opacity: 0.76;
|
||
--sfd-code-opacity: 0.05;
|
||
--sfd-muted-opacity: 0.68;
|
||
}
|
||
|
||
@media (width <= 960px) {
|
||
.subscribe-files-dialog {
|
||
border: 0;
|
||
border-radius: 0 !important;
|
||
}
|
||
|
||
.subscribe-files-shell {
|
||
min-block-size: 100dvh;
|
||
}
|
||
|
||
.subscribe-files-hero {
|
||
min-block-size: auto;
|
||
}
|
||
|
||
.subscribe-files-hero__content {
|
||
display: grid;
|
||
gap: 1rem;
|
||
grid-template-columns: 8rem minmax(0, 1fr);
|
||
padding: 1rem;
|
||
}
|
||
|
||
.subscribe-files-poster-card {
|
||
border-radius: 1rem;
|
||
}
|
||
|
||
.subscribe-files-poster-card__caption {
|
||
display: none;
|
||
}
|
||
|
||
.subscribe-files-hero__title {
|
||
font-size: 1.55rem;
|
||
}
|
||
|
||
.subscribe-files-hero__description {
|
||
display: none;
|
||
}
|
||
|
||
.subscribe-files-stats {
|
||
grid-column: 1 / -1;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
}
|
||
|
||
.subscribe-files-stat-card {
|
||
padding: 0.75rem;
|
||
gap: 0.45rem;
|
||
grid-template-columns: auto minmax(0, 1fr);
|
||
}
|
||
|
||
.subscribe-files-stat-card__content {
|
||
display: flex;
|
||
align-items: baseline;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.subscribe-files-stat-card__label,
|
||
.subscribe-files-stat-card__value {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.subscribe-files-stat-card__icon {
|
||
block-size: 2rem;
|
||
inline-size: 2rem;
|
||
}
|
||
|
||
.subscribe-files-stat-card__value {
|
||
font-size: 1.2rem;
|
||
}
|
||
|
||
.subscribe-files-content {
|
||
display: flex;
|
||
flex: 1 1 auto;
|
||
flex-direction: column;
|
||
min-block-size: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
.subscribe-files-main {
|
||
flex: 1 1 auto;
|
||
border: 0;
|
||
border-radius: 0;
|
||
background: transparent;
|
||
}
|
||
|
||
.subscribe-files-tabs {
|
||
padding: 0.9rem 1rem 0;
|
||
}
|
||
|
||
.subscribe-files-tab-group {
|
||
inline-size: 100%;
|
||
}
|
||
}
|
||
|
||
@media (width <= 560px) {
|
||
.subscribe-files-hero__content {
|
||
grid-template-columns: 6.5rem minmax(0, 1fr);
|
||
}
|
||
|
||
.subscribe-files-stats {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.subscribe-files-stat-card:first-child {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.subscribe-files-mobile-card__header {
|
||
grid-template-columns: auto minmax(0, 1fr);
|
||
}
|
||
|
||
.subscribe-files-mobile-card__header .v-chip {
|
||
justify-self: start;
|
||
grid-column: 2;
|
||
}
|
||
}
|
||
</style>
|