itube-specs 0.0.984 → 0.0.987
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/card/model-mini.vue +8 -6
- package/components/card/model.vue +7 -7
- package/components/card/round.vue +10 -5
- package/components/card/video/index.vue +21 -77
- package/components/info/main.vue +13 -7
- package/components/search/item.vue +2 -2
- package/components/section/swiper-cards.vue +3 -0
- package/components/view/model-videos.vue +2 -1
- package/components/view/models.vue +2 -1
- package/components/view/video.vue +2 -1
- package/components/view/videos.vue +2 -1
- package/package.json +1 -1
- package/pages/categories/[slug].vue +2 -1
- package/pages/channels/[channel].vue +2 -1
- package/pages/following.vue +2 -1
- package/pages/search/[slug]/index.vue +2 -1
|
@@ -52,16 +52,18 @@ const props = defineProps<{
|
|
|
52
52
|
card: IModelCard
|
|
53
53
|
}>();
|
|
54
54
|
|
|
55
|
-
const fallbackImage = '/img/model-placeholder.webp';
|
|
55
|
+
const fallbackImage = '/img/model-placeholder-square.webp';
|
|
56
|
+
const fallbackAvifImage = '/img/model-placeholder-square.avif';
|
|
56
57
|
const imageError = ref(false);
|
|
57
58
|
|
|
58
59
|
/** Аватар 48px круглый — нужен квадратный кроп, вертикальный портрет сюда не годится. */
|
|
59
|
-
const
|
|
60
|
-
if (imageError.value) return fallbackImage;
|
|
61
|
-
return props.card.primaryImageSmallUrl || fallbackImage;
|
|
62
|
-
});
|
|
60
|
+
const isFallback = computed(() => imageError.value || !props.card.primaryImageSmallUrl);
|
|
63
61
|
|
|
64
|
-
const
|
|
62
|
+
const currentImage = computed(() => (isFallback.value ? fallbackImage : props.card.primaryImageSmallUrl));
|
|
63
|
+
|
|
64
|
+
const currentAvifSrc = computed(() => (
|
|
65
|
+
isFallback.value ? fallbackAvifImage : props.card.primaryImageSmallAvifUrl
|
|
66
|
+
));
|
|
65
67
|
</script>
|
|
66
68
|
|
|
67
69
|
<style lang="scss">
|
|
@@ -98,16 +98,16 @@ const { t } = useI18n();
|
|
|
98
98
|
*/
|
|
99
99
|
const SIZES = 'auto, (max-width: 449px) 50vw, (max-width: 1385px) 215px, 250px';
|
|
100
100
|
|
|
101
|
-
const fallbackImage = '/img/model-placeholder.webp';
|
|
101
|
+
const fallbackImage = '/img/model-placeholder-portrait.webp';
|
|
102
|
+
const fallbackAvifImage = '/img/model-placeholder-portrait.avif';
|
|
102
103
|
const imageError = ref(false);
|
|
103
104
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
});
|
|
105
|
+
const isFallback = computed(() => imageError.value || !props.card.primaryImageUrl);
|
|
106
|
+
|
|
107
|
+
const currentImage = computed(() => (isFallback.value ? fallbackImage : props.card.primaryImageUrl));
|
|
108
108
|
|
|
109
|
-
/** На заглушке
|
|
110
|
-
const currentAvifSrc = computed(() => (
|
|
109
|
+
/** На заглушке подменяем и avif-ветку — иначе браузер продолжит тянуть битый кандидат. */
|
|
110
|
+
const currentAvifSrc = computed(() => (isFallback.value ? fallbackAvifImage : props.card.primaryImageAvifUrl));
|
|
111
111
|
|
|
112
112
|
function onImageError() {
|
|
113
113
|
imageError.value = true;
|
|
@@ -49,6 +49,8 @@ const props = withDefaults(defineProps<{
|
|
|
49
49
|
loading?: 'eager' | 'lazy'
|
|
50
50
|
priority?: boolean
|
|
51
51
|
placeholderAvatar?: string
|
|
52
|
+
/** Та же заглушка в avif. У канальной (svg) пары нет — тогда проп не передают. */
|
|
53
|
+
placeholderAvatarAvif?: string
|
|
52
54
|
/** Только аватар и название — без счётчика роликов (страница подписок) */
|
|
53
55
|
withoutCount?: boolean
|
|
54
56
|
}>(), {
|
|
@@ -64,12 +66,15 @@ const SIZES = `auto, (max-width: ${CSS_BREAKPOINTS.sm - 1}px) 80px, 96px`;
|
|
|
64
66
|
const fallbackImage = props.placeholderAvatar;
|
|
65
67
|
const imageError = ref(false);
|
|
66
68
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const isFallback = computed(() => imageError.value || !props.card.primaryImageUrl);
|
|
70
|
+
|
|
71
|
+
const currentImage = computed(() => (
|
|
72
|
+
(isFallback.value ? fallbackImage : props.card.primaryImageUrl) || ''
|
|
73
|
+
));
|
|
71
74
|
|
|
72
|
-
const currentAvifSrc = computed(() => (
|
|
75
|
+
const currentAvifSrc = computed(() => (
|
|
76
|
+
isFallback.value ? props.placeholderAvatarAvif : props.card.primaryImageAvifUrl
|
|
77
|
+
));
|
|
73
78
|
|
|
74
79
|
function onImageError() {
|
|
75
80
|
imageError.value = true;
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
{'--mini': mini},
|
|
6
6
|
{'--compact': compact},
|
|
7
7
|
{'--avatar': card.channelAvatar},
|
|
8
|
-
{'--no-channel': isChannelPage},
|
|
9
8
|
{'--progress': hasProgress},
|
|
10
9
|
{'--favorite': isFavoriteCard},
|
|
11
10
|
{'--tags-open': isTagsOpen},
|
|
@@ -95,30 +94,10 @@
|
|
|
95
94
|
class="card-video__title _truncate"
|
|
96
95
|
>{{ card.title }}
|
|
97
96
|
</NuxtLink>
|
|
98
|
-
<component
|
|
99
|
-
v-if="!mini && !compact && !history && card.channelName"
|
|
100
|
-
:is="component"
|
|
101
|
-
:to="channelLink"
|
|
102
|
-
class="card-video__channel-wrapper"
|
|
103
|
-
>
|
|
104
|
-
<span
|
|
105
|
-
:data-nosnippet="!card.channelName || null"
|
|
106
|
-
class="card-video__channel-title"
|
|
107
|
-
>
|
|
108
|
-
<template v-if="card.channelName">
|
|
109
|
-
{{ card.channelName }}
|
|
110
|
-
</template>
|
|
111
|
-
</span>
|
|
112
|
-
</component>
|
|
113
97
|
<p
|
|
114
98
|
v-if="history"
|
|
115
99
|
class="card-video__meta"
|
|
116
100
|
>
|
|
117
|
-
<NuxtLink
|
|
118
|
-
v-if="card.channelName"
|
|
119
|
-
:to="channelLink"
|
|
120
|
-
class="card-video__channel-title"
|
|
121
|
-
>{{ card.channelName }}</NuxtLink>
|
|
122
101
|
<span
|
|
123
102
|
:class="{'--remaining': remaining}"
|
|
124
103
|
class="card-video__watched"
|
|
@@ -189,9 +168,6 @@ const component = computed(() => props.card.channelName ? NuxtLinkComponent : 'd
|
|
|
189
168
|
|
|
190
169
|
const { generateLink } = useGenerateLink();
|
|
191
170
|
|
|
192
|
-
const pageName = useGetPureRouteName();
|
|
193
|
-
const isChannelPage = computed(() => pageName === 'channels-channel');
|
|
194
|
-
|
|
195
171
|
const previewId = useId();
|
|
196
172
|
const { activePreviewId, startPreviewByHover, stopPreviewByHover, startPreviewByTouch } = useVideoPreview();
|
|
197
173
|
|
|
@@ -383,7 +359,6 @@ $dropdown-column: 18px;
|
|
|
383
359
|
grid-column: 2;
|
|
384
360
|
}
|
|
385
361
|
|
|
386
|
-
.--avatar .card-video__channel-wrapper,
|
|
387
362
|
.--avatar .card-video__meta {
|
|
388
363
|
grid-column: 2;
|
|
389
364
|
}
|
|
@@ -398,25 +373,26 @@ $dropdown-column: 18px;
|
|
|
398
373
|
grid-column: 2 / -1;
|
|
399
374
|
}
|
|
400
375
|
|
|
376
|
+
/* Кружок канала — только на десктопе: на мобиле карточка отдаёт всю ширину заголовку */
|
|
401
377
|
@include to-br(sm) {
|
|
402
|
-
|
|
403
|
-
.--no-channel .card-video__channel-wrapper {
|
|
378
|
+
.card-video .card-video__logo-wrapper {
|
|
404
379
|
display: none;
|
|
405
380
|
}
|
|
406
381
|
|
|
407
|
-
|
|
382
|
+
.card-video .card-video__wrapper {
|
|
408
383
|
grid-template-columns: minmax(0, 1fr) $dropdown-column;
|
|
409
384
|
}
|
|
410
385
|
|
|
411
|
-
|
|
386
|
+
.card-video .card-video__title,
|
|
387
|
+
.card-video .card-video__meta {
|
|
412
388
|
grid-column: 1;
|
|
413
389
|
}
|
|
414
390
|
|
|
415
|
-
|
|
391
|
+
.card-video .card-video__tags {
|
|
416
392
|
grid-column: 1 / -1;
|
|
417
393
|
}
|
|
418
394
|
|
|
419
|
-
|
|
395
|
+
.card-video .card-video__dropdown {
|
|
420
396
|
grid-column: 2;
|
|
421
397
|
grid-row: 1;
|
|
422
398
|
}
|
|
@@ -426,7 +402,7 @@ $dropdown-column: 18px;
|
|
|
426
402
|
grid-template-columns: minmax(0, 1fr) $dropdown-column;
|
|
427
403
|
}
|
|
428
404
|
|
|
429
|
-
/* Без
|
|
405
|
+
/* Без тегов вторая строка грида не нужна — иначе кружок и «троеточие» растягиваются. */
|
|
430
406
|
.--compact .card-video__logo-wrapper,
|
|
431
407
|
.--compact .card-video__dropdown {
|
|
432
408
|
grid-row: 1;
|
|
@@ -455,23 +431,7 @@ $dropdown-column: 18px;
|
|
|
455
431
|
}
|
|
456
432
|
}
|
|
457
433
|
|
|
458
|
-
|
|
459
|
-
@include font-xs;
|
|
460
|
-
|
|
461
|
-
font-weight: 400;
|
|
462
|
-
display: grid;
|
|
463
|
-
grid-row: 2;
|
|
464
|
-
grid-template-columns: auto 1fr;
|
|
465
|
-
align-items: center;
|
|
466
|
-
column-gap: 4px;
|
|
467
|
-
margin-top: 6px;
|
|
468
|
-
|
|
469
|
-
@include to-cq($layout-breakpoint) {
|
|
470
|
-
grid-column: 1;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/* Мета-строка истории: «канал · когда смотрели / сколько осталось» на месте строки канала */
|
|
434
|
+
/* Мета-строка истории: когда смотрели / сколько осталось */
|
|
475
435
|
.card-video__meta {
|
|
476
436
|
@include font-xs;
|
|
477
437
|
|
|
@@ -488,19 +448,6 @@ $dropdown-column: 18px;
|
|
|
488
448
|
}
|
|
489
449
|
}
|
|
490
450
|
|
|
491
|
-
/* На узкой карточке (две колонки на телефоне) «канал · время» в строку не встают, а нешрикуемое
|
|
492
|
-
время задаёт колонке min-content и разносит грид — оставляем только время просмотра */
|
|
493
|
-
@include to-cq($layout-breakpoint) {
|
|
494
|
-
.card-video__meta .card-video__channel-title {
|
|
495
|
-
display: none;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
.card-video__meta .card-video__channel-title + .card-video__watched::before {
|
|
500
|
-
content: '·';
|
|
501
|
-
margin-inline: 6px;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
451
|
.card-video__watched {
|
|
505
452
|
display: flex;
|
|
506
453
|
align-items: center;
|
|
@@ -519,29 +466,24 @@ $dropdown-column: 18px;
|
|
|
519
466
|
border-radius: 50%;
|
|
520
467
|
}
|
|
521
468
|
|
|
469
|
+
/* Кружок раскидан на обе строки: его 36px набираются заголовком плюс плашками, иначе под
|
|
470
|
+
однострочным заголовком остаётся пустой хвост высотой в остаток кружка */
|
|
522
471
|
.card-video__logo-wrapper {
|
|
523
472
|
grid-row: 1/3;
|
|
524
473
|
grid-column: 1;
|
|
525
474
|
}
|
|
526
475
|
|
|
527
|
-
|
|
528
|
-
.card-
|
|
476
|
+
/* У видео без моделей плашек нет вовсе — второй строки тоже, кружку спан не по чему набирать */
|
|
477
|
+
.card-video__wrapper:not(:has(.card-video__tags, .card-video__meta)) .card-video__logo-wrapper {
|
|
478
|
+
grid-row: 1;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.card-video__logo-wrapper[href] {
|
|
529
482
|
@include hover(opacity) {
|
|
530
483
|
opacity: 0.9;
|
|
531
484
|
}
|
|
532
485
|
}
|
|
533
486
|
|
|
534
|
-
.card-video__channel-title {
|
|
535
|
-
@include font-xs;
|
|
536
|
-
|
|
537
|
-
font-weight: 400;
|
|
538
|
-
color: $video-card-secondary;
|
|
539
|
-
overflow: hidden;
|
|
540
|
-
white-space: nowrap;
|
|
541
|
-
text-overflow: ellipsis;
|
|
542
|
-
max-width: 100%;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
487
|
.card-video.--mini {
|
|
546
488
|
row-gap: 8px;
|
|
547
489
|
}
|
|
@@ -561,13 +503,15 @@ $dropdown-column: 18px;
|
|
|
561
503
|
z-index: $z-index-dropdown;
|
|
562
504
|
}
|
|
563
505
|
|
|
564
|
-
/* Ряд плашек лежит ниже «троеточия» — колонка под меню в этом ряду свободна, отдаём её
|
|
506
|
+
/* Ряд плашек лежит ниже «троеточия» — колонка под меню в этом ряду свободна, отдаём её плашкам.
|
|
507
|
+
Строка задана явно: на авторазмещении плашки уезжали в третью строку, оставляя пустую вторую */
|
|
565
508
|
.card-video__tags {
|
|
509
|
+
grid-row: 2;
|
|
566
510
|
grid-column: 1 / -1;
|
|
567
511
|
|
|
568
512
|
@include from-cq($layout-breakpoint) {
|
|
569
513
|
grid-column: 2 / -1;
|
|
570
|
-
margin-top:
|
|
514
|
+
margin-top: 6px;
|
|
571
515
|
}
|
|
572
516
|
}
|
|
573
517
|
|
package/components/info/main.vue
CHANGED
|
@@ -204,20 +204,25 @@ function onFollowClick() {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
const placeholders: Record<string, string> = {
|
|
207
|
-
model: '/img/model-placeholder.webp',
|
|
207
|
+
model: '/img/model-placeholder-square.webp',
|
|
208
208
|
channel: '/img/logo-placeholder.svg',
|
|
209
209
|
};
|
|
210
|
-
|
|
210
|
+
/** У канала заглушка — svg, avif-пары у неё нет. */
|
|
211
|
+
const avifPlaceholders: Record<string, string | undefined> = {
|
|
212
|
+
model: '/img/model-placeholder-square.avif',
|
|
213
|
+
};
|
|
214
|
+
const placeholderType = computed(() => props.type || 'channel');
|
|
215
|
+
const placeholder = computed(() => placeholders[ placeholderType.value ]);
|
|
211
216
|
const imageError = ref(false);
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
return props.card.img || placeholder.value;
|
|
215
|
-
});
|
|
217
|
+
const isFallback = computed(() => imageError.value || !props.card.img);
|
|
218
|
+
const currentImage = computed(() => (isFallback.value ? placeholder.value : props.card.img));
|
|
216
219
|
|
|
217
220
|
/** `.info-main__img` — 80px до sm, 96px дальше. `auto` впереди — см. `UiImg`. */
|
|
218
221
|
const IMG_SIZES = `auto, (max-width: ${CSS_BREAKPOINTS.sm - 1}px) 80px, 96px`;
|
|
219
222
|
|
|
220
|
-
const currentAvifSrc = computed(() => (
|
|
223
|
+
const currentAvifSrc = computed(() => (
|
|
224
|
+
isFallback.value ? avifPlaceholders[ placeholderType.value ] : props.card.imgAvifUrl
|
|
225
|
+
));
|
|
221
226
|
|
|
222
227
|
function getParameter(name: string) {
|
|
223
228
|
return props.card.parameters?.find(p => p.name === name)?.values?.[ 0 ]?.name;
|
|
@@ -296,6 +301,7 @@ const socialItems = computed<ILinkItem[]>(() => {
|
|
|
296
301
|
|
|
297
302
|
.info-main.--model .info-main__content {
|
|
298
303
|
padding: 16px;
|
|
304
|
+
grid-template-columns: auto 1fr;
|
|
299
305
|
|
|
300
306
|
@include from-br(sm) {
|
|
301
307
|
padding: 20px;
|
|
@@ -46,8 +46,8 @@ const icon = computed(() => {
|
|
|
46
46
|
const square = pickPortraitVariant(props.item?.portraits)?.square;
|
|
47
47
|
|
|
48
48
|
return {
|
|
49
|
-
webp: square?.webp || '/img/model-placeholder.webp',
|
|
50
|
-
avif: square?.avif,
|
|
49
|
+
webp: square?.webp || '/img/model-placeholder-square.webp',
|
|
50
|
+
avif: square?.webp ? square.avif : '/img/model-placeholder-square.avif',
|
|
51
51
|
};
|
|
52
52
|
});
|
|
53
53
|
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
<CardRound
|
|
39
39
|
:card="item"
|
|
40
40
|
:placeholder-avatar="placeholderAvatar"
|
|
41
|
+
:placeholder-avatar-avif="placeholderAvatarAvif"
|
|
41
42
|
:without-count="withoutCount"
|
|
42
43
|
class="section-swiper-cards__card"
|
|
43
44
|
/>
|
|
@@ -79,6 +80,8 @@ withDefaults(defineProps<{
|
|
|
79
80
|
/** Вариант заголовка — см. `UiSectionTitle` */
|
|
80
81
|
variant?: 'page' | 'section'
|
|
81
82
|
placeholderAvatar?: string
|
|
83
|
+
/** Та же заглушка в avif — см. `CardRound` */
|
|
84
|
+
placeholderAvatarAvif?: string
|
|
82
85
|
/** Карточки только с аватаром и названием, без счётчика роликов */
|
|
83
86
|
withoutCount?: boolean
|
|
84
87
|
}>(), {
|
|
@@ -92,7 +92,8 @@
|
|
|
92
92
|
v-if="similarModels && similarModels.length > 0"
|
|
93
93
|
:items="modelsRoundCards"
|
|
94
94
|
:title="$t('similar_models')"
|
|
95
|
-
placeholder-avatar="/img/model-placeholder.webp"
|
|
95
|
+
placeholder-avatar="/img/model-placeholder-square.webp"
|
|
96
|
+
placeholder-avatar-avif="/img/model-placeholder-square.avif"
|
|
96
97
|
class="view-model-videos__other-models"
|
|
97
98
|
hydrate-on-visible
|
|
98
99
|
/>
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
v-if="topCards.length >= MINIMUM_TOP_THUMBS"
|
|
7
7
|
:items="topCards"
|
|
8
8
|
class="view-models__popular"
|
|
9
|
-
placeholder-avatar="/img/model-placeholder.webp"
|
|
9
|
+
placeholder-avatar="/img/model-placeholder-square.webp"
|
|
10
|
+
placeholder-avatar-avif="/img/model-placeholder-square.avif"
|
|
10
11
|
only-grid
|
|
11
12
|
/>
|
|
12
13
|
|
|
@@ -91,7 +91,8 @@
|
|
|
91
91
|
:items="modelsRoundCards"
|
|
92
92
|
:title="$t('related_models')"
|
|
93
93
|
class="view-video__popular-models"
|
|
94
|
-
placeholder-avatar="/img/model-placeholder.webp"
|
|
94
|
+
placeholder-avatar="/img/model-placeholder-square.webp"
|
|
95
|
+
placeholder-avatar-avif="/img/model-placeholder-square.avif"
|
|
95
96
|
hydrate-on-visible
|
|
96
97
|
/>
|
|
97
98
|
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
:all-link-text="$t('show_all_models')"
|
|
50
50
|
:header-link-text-short="$t('all_models')"
|
|
51
51
|
class="view-videos__popular-models"
|
|
52
|
-
placeholder-avatar="/img/model-placeholder.webp"
|
|
52
|
+
placeholder-avatar="/img/model-placeholder-square.webp"
|
|
53
|
+
placeholder-avatar-avif="/img/model-placeholder-square.avif"
|
|
53
54
|
hydrate-on-visible
|
|
54
55
|
/>
|
|
55
56
|
</GridVideos>
|
package/package.json
CHANGED
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
:items="modelsRoundCards"
|
|
56
56
|
:title="$t('category_top_models_title', {name: title})"
|
|
57
57
|
class="category__models"
|
|
58
|
-
placeholder-avatar="/img/model-placeholder.webp"
|
|
58
|
+
placeholder-avatar="/img/model-placeholder-square.webp"
|
|
59
|
+
placeholder-avatar-avif="/img/model-placeholder-square.avif"
|
|
59
60
|
/>
|
|
60
61
|
</template>
|
|
61
62
|
</GridVideos>
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
:items="modelsRoundCards"
|
|
53
53
|
:title="t('channels_popular_models')"
|
|
54
54
|
class="channel__videos-models"
|
|
55
|
-
placeholder-avatar="/img/model-placeholder.webp"
|
|
55
|
+
placeholder-avatar="/img/model-placeholder-square.webp"
|
|
56
|
+
placeholder-avatar-avif="/img/model-placeholder-square.avif"
|
|
56
57
|
hydrate-on-visible
|
|
57
58
|
/>
|
|
58
59
|
</template>
|
package/pages/following.vue
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
all-link="/library/models"
|
|
19
19
|
icon="user"
|
|
20
20
|
variant="section"
|
|
21
|
-
placeholder-avatar="/img/model-placeholder.webp"
|
|
21
|
+
placeholder-avatar="/img/model-placeholder-square.webp"
|
|
22
|
+
placeholder-avatar-avif="/img/model-placeholder-square.avif"
|
|
22
23
|
class="following__section"
|
|
23
24
|
without-count
|
|
24
25
|
/>
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
<template #default="item">
|
|
50
50
|
<CardRound
|
|
51
51
|
:card="item"
|
|
52
|
-
placeholder-avatar="/img/model-placeholder.webp"
|
|
52
|
+
placeholder-avatar="/img/model-placeholder-square.webp"
|
|
53
|
+
placeholder-avatar-avif="/img/model-placeholder-square.avif"
|
|
53
54
|
/>
|
|
54
55
|
</template>
|
|
55
56
|
<template
|