itube-specs 0.0.958 → 0.0.959
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.
|
@@ -868,6 +868,8 @@ $bg-fade-90: $color-zinc-950-90; // почти непрозрачный
|
|
|
868
868
|
// ─── Model card ───
|
|
869
869
|
$model-card-bg: $color-zinc-900-50;
|
|
870
870
|
$model-card-bg--hover: $color-zinc-800-50;
|
|
871
|
+
$model-card-thumb-bg: $color-zinc-800;
|
|
872
|
+
$model-card-separator: $color-zinc-600;
|
|
871
873
|
$model-card-border: $color-zinc-800-50;
|
|
872
874
|
$model-card-border--hover: $color-zinc-700-50;
|
|
873
875
|
$model-card-params: $color-zinc-500;
|
|
@@ -894,6 +894,8 @@ $bg-fade-90: $color-gray-950-90; // почти непрозрачный
|
|
|
894
894
|
// ─── Model card ───
|
|
895
895
|
$model-card-bg: $color-gray-100;
|
|
896
896
|
$model-card-bg--hover: $model-card-bg;
|
|
897
|
+
$model-card-thumb-bg: $color-gray-200;
|
|
898
|
+
$model-card-separator: $color-gray-400;
|
|
897
899
|
$model-card-border: transparent;
|
|
898
900
|
$model-card-border--hover: $model-card-border;
|
|
899
901
|
$model-card-params: $color-gray-600;
|
|
@@ -1,82 +1,83 @@
|
|
|
1
|
-
<!-- Карточка модели:
|
|
1
|
+
<!-- Карточка модели: портрет 3:4, имя со страной, параметры и счётчики видео/обновлений -->
|
|
2
2
|
<template>
|
|
3
3
|
<NuxtLink
|
|
4
4
|
:to="generateLink(`/models/${convertString().toSlug(card.title)}`)"
|
|
5
5
|
:title="card.title"
|
|
6
6
|
class="card-model"
|
|
7
7
|
>
|
|
8
|
-
<div class="card-
|
|
8
|
+
<div class="card-model__thumb">
|
|
9
9
|
<UiImg
|
|
10
10
|
:src="currentImage"
|
|
11
11
|
:loading="loading"
|
|
12
12
|
:alt="card.title"
|
|
13
13
|
:priority="priority"
|
|
14
|
+
:sizes="`(max-width: ${CSS_BREAKPOINTS.xs}px) 50vw, 240px`"
|
|
14
15
|
class="card-model__img"
|
|
15
|
-
width="
|
|
16
|
-
height="
|
|
17
|
-
sizes="xs:64px sm:64px md:64px lg:64px"
|
|
16
|
+
width="240"
|
|
17
|
+
height="320"
|
|
18
18
|
@error="onImageError"
|
|
19
19
|
/>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</span>
|
|
38
|
-
</p>
|
|
39
|
-
<p class="card-model__parameters">
|
|
40
|
-
<span
|
|
41
|
-
v-for="(parameter, index) in card.parameters"
|
|
42
|
-
:key="`parameter-${index}`"
|
|
43
|
-
class="card-model__parameter"
|
|
44
|
-
>
|
|
45
|
-
<span
|
|
46
|
-
v-if="parameter.title"
|
|
47
|
-
class="card-model__parameter-title"
|
|
48
|
-
>{{ getValue(parameter) }}</span>
|
|
49
|
-
</span>
|
|
50
|
-
</p>
|
|
51
|
-
<p class="card-model__parameters-footer">
|
|
52
|
-
<span
|
|
53
|
-
v-if="card.videosCount"
|
|
54
|
-
class="card-model__count"
|
|
20
|
+
</div>
|
|
21
|
+
<div class="card-model__wrapper">
|
|
22
|
+
<p class="card-model__info">
|
|
23
|
+
<span class="card-model__info-name">
|
|
24
|
+
{{ card.title }}
|
|
25
|
+
</span>
|
|
26
|
+
<span
|
|
27
|
+
v-if="card.country?.name"
|
|
28
|
+
:title="`${t('birthplace_country')}: ${card.country.title}`"
|
|
29
|
+
class="card-model__country-wrapper"
|
|
30
|
+
>
|
|
31
|
+
<img
|
|
32
|
+
:alt="card.country.title"
|
|
33
|
+
:src="`/icons/flags/${card.country.name}.svg`"
|
|
34
|
+
class="card-model__info-country"
|
|
35
|
+
width="16"
|
|
36
|
+
height="12"
|
|
55
37
|
>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
38
|
+
</span>
|
|
39
|
+
</p>
|
|
40
|
+
<p class="card-model__parameters">
|
|
41
|
+
<span
|
|
42
|
+
v-for="(parameter, index) in visibleParameters"
|
|
43
|
+
:key="`parameter-${index}`"
|
|
44
|
+
class="card-model__parameter"
|
|
45
|
+
>
|
|
62
46
|
<span
|
|
63
|
-
v-if="
|
|
64
|
-
class="card-
|
|
65
|
-
>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
47
|
+
v-if="parameter.title"
|
|
48
|
+
class="card-model__parameter-title"
|
|
49
|
+
>{{ getValue(parameter) }}</span>
|
|
50
|
+
</span>
|
|
51
|
+
<span
|
|
52
|
+
v-if="card.videosCount"
|
|
53
|
+
class="card-model__count"
|
|
54
|
+
>
|
|
55
|
+
<UiIcon
|
|
56
|
+
name="video-camera"
|
|
57
|
+
size="14"
|
|
58
|
+
/>
|
|
59
|
+
{{ $n(card.videosCount, 'compact') }}
|
|
60
|
+
</span>
|
|
61
|
+
</p>
|
|
62
|
+
<p
|
|
63
|
+
v-if="card.lastVideoAddedAt"
|
|
64
|
+
class="card-model__update"
|
|
65
|
+
>
|
|
66
|
+
<UiIcon
|
|
67
|
+
name="time-updated"
|
|
68
|
+
size="12"
|
|
69
|
+
/>
|
|
70
|
+
<span class="card-model__update-text">
|
|
71
|
+
{{ t('library.updated_at', { date: formatDate(card.lastVideoAddedAt) }) }}
|
|
72
|
+
</span>
|
|
73
|
+
</p>
|
|
74
74
|
</div>
|
|
75
75
|
</NuxtLink>
|
|
76
76
|
</template>
|
|
77
77
|
|
|
78
78
|
<script setup lang="ts">
|
|
79
79
|
import { convertString, formatDate } from '../../runtime';
|
|
80
|
+
import { CSS_BREAKPOINTS } from '../../config/css-breakpoints';
|
|
80
81
|
import type { IModelCard } from '../../types';
|
|
81
82
|
|
|
82
83
|
const props = withDefaults(defineProps<{
|
|
@@ -102,6 +103,16 @@ function onImageError() {
|
|
|
102
103
|
imageError.value = true;
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
/** В строку помещается только первый параметр из списка (тип парня) и возраст — остальные скрыты. */
|
|
107
|
+
const visibleParameters = computed(() => {
|
|
108
|
+
const isAge = (item: IModelCard['parameters'][number]) => item.title === 'age';
|
|
109
|
+
|
|
110
|
+
return [
|
|
111
|
+
props.card.parameters.find(item => !isAge(item)),
|
|
112
|
+
props.card.parameters.find(isAge),
|
|
113
|
+
].filter(item => item !== undefined);
|
|
114
|
+
});
|
|
115
|
+
|
|
105
116
|
function getValue(item: { title?: string; value?: string }) {
|
|
106
117
|
if (item.title === 'age') {
|
|
107
118
|
return `${item.value} ${t('yo')}`;
|
|
@@ -112,11 +123,12 @@ function getValue(item: { title?: string; value?: string }) {
|
|
|
112
123
|
|
|
113
124
|
<style lang="scss">
|
|
114
125
|
.card-model {
|
|
115
|
-
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-direction: column;
|
|
116
128
|
background-color: $model-card-bg;
|
|
117
|
-
border-radius: $radius-
|
|
129
|
+
border-radius: $radius-md;
|
|
118
130
|
border: 1px solid $model-card-border;
|
|
119
|
-
|
|
131
|
+
overflow: hidden;
|
|
120
132
|
|
|
121
133
|
@include hover(border-color, background-color, box-shadow) {
|
|
122
134
|
border-color: $model-card-border--hover;
|
|
@@ -125,52 +137,48 @@ function getValue(item: { title?: string; value?: string }) {
|
|
|
125
137
|
}
|
|
126
138
|
}
|
|
127
139
|
|
|
128
|
-
.card-
|
|
129
|
-
|
|
140
|
+
.card-model__thumb {
|
|
141
|
+
aspect-ratio: 3/4;
|
|
142
|
+
background-color: $model-card-thumb-bg;
|
|
143
|
+
overflow: hidden;
|
|
144
|
+
}
|
|
130
145
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
.card-model__img {
|
|
147
|
+
width: 100%;
|
|
148
|
+
height: 100%;
|
|
149
|
+
object-fit: cover;
|
|
150
|
+
object-position: center top;
|
|
135
151
|
|
|
136
152
|
@include from-br(sm) {
|
|
137
|
-
|
|
138
|
-
column-gap: 12px;
|
|
153
|
+
transition: var(--transition) transform;
|
|
139
154
|
}
|
|
140
155
|
}
|
|
141
156
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
display: flex;
|
|
147
|
-
flex-wrap: nowrap;
|
|
148
|
-
align-items: center;
|
|
157
|
+
@media (hover: hover) {
|
|
158
|
+
.card-model:is(:hover, :focus-visible) .card-model__img {
|
|
159
|
+
transform: scale(1.05);
|
|
160
|
+
}
|
|
149
161
|
}
|
|
150
162
|
|
|
151
|
-
.card-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
display: flex;
|
|
155
|
-
flex-wrap: nowrap;
|
|
156
|
-
align-items: center;
|
|
157
|
-
color: $model-card-params-footer;
|
|
158
|
-
margin-top: 2px;
|
|
159
|
-
column-gap: 12px;
|
|
163
|
+
.card-model__wrapper {
|
|
164
|
+
padding: 8px;
|
|
160
165
|
}
|
|
161
166
|
|
|
162
|
-
.card-
|
|
167
|
+
.card-model__info {
|
|
163
168
|
display: grid;
|
|
164
|
-
grid-template-columns:
|
|
165
|
-
column-gap:
|
|
169
|
+
grid-template-columns: minmax(0, auto) auto;
|
|
170
|
+
column-gap: 6px;
|
|
171
|
+
justify-content: start;
|
|
166
172
|
align-items: center;
|
|
173
|
+
margin-bottom: 2px;
|
|
167
174
|
}
|
|
168
175
|
|
|
169
176
|
.card-model__info-name {
|
|
177
|
+
@include font-sm;
|
|
178
|
+
|
|
170
179
|
font-weight: 500;
|
|
171
180
|
color: $model-card-name;
|
|
172
181
|
white-space: nowrap;
|
|
173
|
-
max-width: 100%;
|
|
174
182
|
text-overflow: ellipsis;
|
|
175
183
|
overflow: hidden;
|
|
176
184
|
text-transform: capitalize;
|
|
@@ -186,50 +194,64 @@ function getValue(item: { title?: string; value?: string }) {
|
|
|
186
194
|
}
|
|
187
195
|
}
|
|
188
196
|
|
|
189
|
-
.card-
|
|
190
|
-
display:
|
|
191
|
-
column-gap: 8px;
|
|
192
|
-
margin-bottom: 4px;
|
|
193
|
-
grid-template-columns: auto auto auto;
|
|
194
|
-
justify-content: start;
|
|
195
|
-
align-items: center;
|
|
197
|
+
.card-model__country-wrapper {
|
|
198
|
+
display: flex;
|
|
196
199
|
}
|
|
197
200
|
|
|
198
|
-
.card-
|
|
201
|
+
.card-model__info-country {
|
|
202
|
+
border-radius: 2px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.card-model__parameters {
|
|
199
206
|
@include font-sm;
|
|
200
207
|
|
|
201
208
|
display: flex;
|
|
209
|
+
flex-wrap: nowrap;
|
|
202
210
|
align-items: center;
|
|
203
|
-
|
|
204
|
-
color: $model-card-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
.card-model__img {
|
|
208
|
-
width: var(--img-size);
|
|
209
|
-
height: var(--img-size);
|
|
210
|
-
border-radius: $radius-xl;
|
|
211
|
-
object-fit: cover;
|
|
212
|
-
object-position: center 70%;
|
|
211
|
+
overflow: hidden;
|
|
212
|
+
color: $model-card-params;
|
|
213
|
+
margin-bottom: 2px;
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
.card-model__parameter:not(:last-child) {
|
|
216
|
-
position: relative;
|
|
217
217
|
display: grid;
|
|
218
|
-
grid-template-columns: 1fr auto;
|
|
218
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
219
219
|
column-gap: 6px;
|
|
220
220
|
padding-right: 6px;
|
|
221
221
|
|
|
222
222
|
&::after {
|
|
223
223
|
content: '·';
|
|
224
|
+
color: $model-card-separator;
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
|
|
227
|
-
.card-model__parameter {
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
.card-model__parameter-title {
|
|
229
|
+
white-space: nowrap;
|
|
230
|
+
text-overflow: ellipsis;
|
|
231
|
+
overflow: hidden;
|
|
230
232
|
}
|
|
231
233
|
|
|
232
|
-
.card-
|
|
233
|
-
|
|
234
|
+
.card-model__count {
|
|
235
|
+
display: flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
column-gap: 4px;
|
|
238
|
+
flex-shrink: 0;
|
|
239
|
+
color: $model-card-count;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.card-model__update {
|
|
243
|
+
@include font-sm;
|
|
244
|
+
|
|
245
|
+
display: grid;
|
|
246
|
+
grid-template-columns: 12px minmax(0, 1fr);
|
|
247
|
+
column-gap: 4px;
|
|
248
|
+
align-items: center;
|
|
249
|
+
color: $model-card-params-footer;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.card-model__update-text {
|
|
253
|
+
white-space: nowrap;
|
|
254
|
+
text-overflow: ellipsis;
|
|
255
|
+
overflow: hidden;
|
|
234
256
|
}
|
|
235
257
|
</style>
|
|
@@ -26,15 +26,27 @@ defineProps<{
|
|
|
26
26
|
</script>
|
|
27
27
|
|
|
28
28
|
<style lang="scss">
|
|
29
|
+
/* Лестница колонок как в макете: 2 → 3 (640px) → 4 (768px) → 5 (1024px) → 6 (1280px).
|
|
30
|
+
Пороги — литералы макета, с тирами $brkpnts намеренно не совпадают. */
|
|
29
31
|
.grid-models {
|
|
30
|
-
--min-width: 280px;
|
|
31
|
-
|
|
32
32
|
display: grid;
|
|
33
|
-
grid-template-columns: repeat(
|
|
33
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
34
34
|
gap: 12px;
|
|
35
35
|
|
|
36
|
-
@include from-br(
|
|
37
|
-
|
|
36
|
+
@include from-br(640px) {
|
|
37
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@include from-br(768px) {
|
|
41
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@include from-br(1024px) {
|
|
45
|
+
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@include from-br(1280px) {
|
|
49
|
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
38
50
|
}
|
|
39
51
|
}
|
|
40
52
|
</style>
|
package/package.json
CHANGED
package/pages/models/index.vue
CHANGED
|
@@ -46,11 +46,14 @@ const testModelImages = [
|
|
|
46
46
|
'/img/models/Sebastian _Kane.png',
|
|
47
47
|
];
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
/** Портреты 3:4 — только под карточку модели, круглым аватарам топа они не подходят. */
|
|
50
|
+
const testModelPortraits = Array.from({ length: 16 }, (_, index) => `/img/models/portrait-${index + 1}.png`);
|
|
51
|
+
|
|
52
|
+
function applyTestImages<T extends { primaryImageUrl?: string; thumbUrl?: string }>(items: T[] | undefined, images: string[]): T[] | undefined {
|
|
50
53
|
return items?.map((item, i) => ({
|
|
51
54
|
...item,
|
|
52
|
-
primaryImageUrl:
|
|
53
|
-
thumbUrl:
|
|
55
|
+
primaryImageUrl: images[i % images.length],
|
|
56
|
+
thumbUrl: images[i % images.length],
|
|
54
57
|
}));
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -63,10 +66,10 @@ const [
|
|
|
63
66
|
]);
|
|
64
67
|
|
|
65
68
|
if (topModels.value?.items) {
|
|
66
|
-
topModels.value.items = applyTestImages(topModels.value.items)!;
|
|
69
|
+
topModels.value.items = applyTestImages(topModels.value.items, testModelImages)!;
|
|
67
70
|
}
|
|
68
71
|
if (models.value?.items) {
|
|
69
|
-
models.value.items = applyTestImages(models.value.items)!;
|
|
72
|
+
models.value.items = applyTestImages(models.value.items, testModelPortraits)!;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
const { meta, h1 } = useMeta({
|