itube-specs 0.0.881 → 0.0.885

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.
@@ -13,7 +13,6 @@ $button-variants: (
13
13
  'border': $button-primary-border,
14
14
  'border--hover': $button-primary-border,
15
15
  'border--active': $button-primary-border,
16
- 'icon': $button-primary-text,
17
16
  ),
18
17
  secondary: (
19
18
  'bg': $button-secondary-bg,
@@ -24,7 +23,6 @@ $button-variants: (
24
23
  'border': $button-secondary-border,
25
24
  'border--hover': $button-secondary-border,
26
25
  'border--active': $button-secondary-border--active,
27
- 'icon': $button-secondary-icon,
28
26
  ),
29
27
  muted: (
30
28
  'bg': $button-muted-bg,
@@ -35,7 +33,6 @@ $button-variants: (
35
33
  'border': $button-muted-border,
36
34
  'border--hover': $button-muted-border--hover,
37
35
  'border--active': $button-muted-border,
38
- 'icon': $button-muted-text,
39
36
  ),
40
37
  warning: (
41
38
  'bg': $button-warning-bg,
@@ -46,7 +43,6 @@ $button-variants: (
46
43
  'border': $button-warning-border,
47
44
  'border--hover': $button-warning-border,
48
45
  'border--active': $button-warning-border,
49
- 'icon': $button-warning-text,
50
46
  ),
51
47
  );
52
48
 
@@ -221,6 +221,7 @@ $history-card-dropdown-icon: $color-zinc-500;
221
221
  $history-card-dropdown-icon--hover: $color-white;
222
222
  $history-card-progress-bg: $mono-white-50;
223
223
  $history-card-progress-fill: $color-indigo-500;
224
+ $history-card-remaining: $color-indigo-400;
224
225
 
225
226
  // ─── Video card dropdown ───
226
227
  $video-card-dropdown-border: $color-zinc-800;
@@ -469,7 +470,6 @@ $button-secondary-text: $color-zinc-400;
469
470
  $button-secondary-text--active: $color-white;
470
471
  $button-secondary-border: $color-zinc-700-50;
471
472
  $button-secondary-border--active: $color-indigo-500;
472
- $button-secondary-icon: $color-indigo-600;
473
473
  $button-muted-bg: $color-zinc-800;
474
474
  $button-muted-text: $color-zinc-300;
475
475
  $button-muted-bg--hover: $color-zinc-700;
@@ -723,7 +723,6 @@ $filters-main-slider-border: $color-zinc-800;
723
723
  $filters-main-select-border--active: $color-indigo-500-50;
724
724
  $filters-main-select-bg--active: $color-indigo-600-20;
725
725
  $filters-main-select-text--active: $color-indigo-300;
726
- $filters-main-select-icon: $color-zinc-400;
727
726
  $filters-main-slider-content-bg: $color-zinc-900;
728
727
  $filters-main-bg--hover: $color-zinc-700-70;
729
728
 
@@ -948,10 +947,33 @@ $page-channels-border: $color-zinc-800;
948
947
  // ─── Page: channel ───
949
948
  $page-channel-border: $color-zinc-800;
950
949
 
950
+ // ─── Library item (карточка-ссылка) ───
951
+ $library-item-bg: $color-zinc-900-40;
952
+ $library-item-bg--hover: $color-zinc-900;
953
+ $library-item-border: $color-zinc-800;
954
+ $library-item-border--hover: $color-indigo-500-50;
955
+ $library-item-icon: $color-indigo-400;
956
+ $library-item-icon-bg: $color-indigo-500-10;
957
+ $library-item-icon-bg--hover: $color-indigo-500-20;
958
+ $library-item-icon-border: $color-indigo-500-20;
959
+ $library-item-icon-border--hover: $color-indigo-500-40;
960
+ $library-item-title: $color-white;
961
+ $library-item-title--hover: $color-indigo-400;
962
+ $library-item-subtitle: $color-zinc-500;
963
+ $library-item-badge-bg: $color-indigo-600-90;
964
+ $library-item-badge-text: $color-white;
965
+ $library-item-badge-bordered-border: $color-zinc-600;
966
+ $library-item-badge-bordered-text: $color-zinc-400;
967
+
968
+ // ─── Page: library ───
969
+ $page-library-heading: $color-white;
970
+ $page-library-heading-icon: $color-white;
971
+ $page-library-heading-icon-bg: $mono-white-10;
972
+ $page-library-toggle-text: $color-zinc-300;
973
+
951
974
  // ─── Page: history ───
952
975
  $page-history-group-title: $color-zinc-300;
953
976
  $page-history-group-icon: $color-zinc-500;
954
- $page-history-clear-icon: $color-zinc-400;
955
977
  $page-history-group-count: $color-zinc-500;
956
978
  $page-history-confirm-text: $color-zinc-300;
957
979
  $page-history-confirm-muted: $color-zinc-500;
@@ -279,6 +279,7 @@ $history-card-dropdown-icon: $color-gray-600;
279
279
  $history-card-dropdown-icon--hover: $color-gray-900;
280
280
  $history-card-progress-bg: $color-gray-300;
281
281
  $history-card-progress-fill: $color-indigo-500;
282
+ $history-card-remaining: $color-indigo-600;
282
283
 
283
284
  // ─── Video card dropdown ───
284
285
  $video-card-dropdown-border: transparent;
@@ -526,7 +527,6 @@ $button-secondary-text: $color-gray-700;
526
527
  $button-secondary-text--active: $color-white;
527
528
  $button-secondary-border: transparent;
528
529
  $button-secondary-border--active: $color-indigo-soft;
529
- $button-secondary-icon: $color-gray-700;
530
530
  $button-muted-bg: transparent;
531
531
  $button-muted-text: $color-gray-300;
532
532
  $button-muted-bg--hover: $color-slate-750;
@@ -753,7 +753,6 @@ $filters-main-slider-border: transparent;
753
753
  $filters-main-select-border--active: $color-indigo-500-50;
754
754
  $filters-main-select-bg--active: $color-gray-300;
755
755
  $filters-main-select-text--active: $color-gray-700;
756
- $filters-main-select-icon: $color-gray-700;
757
756
  $filters-main-slider-content-bg: $color-slate-800;
758
757
  $filters-main-bg--hover: $button-secondary-bg--hover;
759
758
 
@@ -978,10 +977,33 @@ $page-channels-border: transparent;
978
977
  // ─── Page: channel ───
979
978
  $page-channel-border: transparent;
980
979
 
980
+ // ─── Library item (карточка-ссылка) ───
981
+ $library-item-bg: $color-gray-100;
982
+ $library-item-bg--hover: $color-gray-200;
983
+ $library-item-border: transparent;
984
+ $library-item-border--hover: $color-indigo-500-50;
985
+ $library-item-icon: $color-indigo-600;
986
+ $library-item-icon-bg: $color-indigo-500-10;
987
+ $library-item-icon-bg--hover: $color-indigo-500-20;
988
+ $library-item-icon-border: transparent;
989
+ $library-item-icon-border--hover: transparent;
990
+ $library-item-title: $color-gray-900;
991
+ $library-item-title--hover: $color-indigo-600;
992
+ $library-item-subtitle: $color-gray-500;
993
+ $library-item-badge-bg: $color-indigo-600;
994
+ $library-item-badge-text: $color-white;
995
+ $library-item-badge-bordered-border: $color-gray-300;
996
+ $library-item-badge-bordered-text: $color-gray-600;
997
+
998
+ // ─── Page: library ───
999
+ $page-library-heading: $color-gray-900;
1000
+ $page-library-heading-icon: $color-gray-900;
1001
+ $page-library-heading-icon-bg: $color-gray-200;
1002
+ $page-library-toggle-text: $color-gray-700;
1003
+
981
1004
  // ─── Page: history ───
982
1005
  $page-history-group-title: $color-gray-900;
983
1006
  $page-history-group-icon: $color-gray-500;
984
- $page-history-clear-icon: $color-gray-400;
985
1007
  $page-history-group-count: $color-gray-500;
986
1008
  $page-history-confirm-text: $color-gray-900;
987
1009
  $page-history-confirm-muted: $color-gray-500;
@@ -52,9 +52,12 @@ $color-indigo-300: lab(74.0235% 8.54138 -41.6075); // #a4b3ff
52
52
  $color-indigo-400: lab(59.866% 22.4834 -64.4485); // #7d87ff
53
53
  $color-indigo-500: lab(48.295% 38.3129 -81.9673); // #625fff
54
54
  $color-indigo-500-10: lab(48.295% 38.3129 -81.9673 / 10%);
55
+ $color-indigo-500-20: lab(48.295% 38.3129 -81.9673 / 20%);
56
+ $color-indigo-500-40: lab(48.295% 38.3129 -81.9673 / 40%);
55
57
  $color-indigo-500-50: lab(48.295% 38.3129 -81.9673 / 50%);
56
58
  $color-indigo-600: lab(38.4009% 52.6132 -92.3857); // #4f39f6
57
59
  $color-indigo-600-20: lab(38.4009% 52.6132 -92.3857 / 20%);
60
+ $color-indigo-600-90: lab(38.4009% 52.6132 -92.3857 / 90%);
58
61
  $color-indigo-700: lab(32.4486% 49.2217 -84.6695); // #432dd7
59
62
  $color-indigo-800: lab(26.6645% 37.9804 -68.6402); // #372aac
60
63
  $color-indigo-900: lab(23.3911% 24.6978 -50.4718); // #312c85
@@ -82,12 +82,15 @@
82
82
  :to="channelLink"
83
83
  class="card-history__channel _truncate"
84
84
  >{{ card.channelName }}</NuxtLink>
85
- <span class="card-history__watched">
85
+ <span
86
+ :class="{'--remaining': remaining}"
87
+ class="card-history__watched"
88
+ >
86
89
  <UiIcon
87
90
  name="clock"
88
91
  size="12"
89
92
  />
90
- {{ watchedAgo }}
93
+ {{ remaining ? remainingText : watchedAgo }}
91
94
  </span>
92
95
  </p>
93
96
 
@@ -127,6 +130,8 @@ const props = defineProps<{
127
130
  card: HistoryStubCard
128
131
  loading?: 'eager' | 'lazy'
129
132
  priority?: boolean
133
+ /** Развилка мета-строки: вместо «когда смотрели» — сколько осталось до конца («Продолжить просмотр») */
134
+ remaining?: boolean
130
135
  }>();
131
136
 
132
137
  const emit = defineEmits<{
@@ -160,6 +165,11 @@ const progress = computed(() => Math.min(100, Math.max(0, Math.round(props.card.
160
165
 
161
166
  const watchedAgo = computed(() => useFormatTimeAgo(props.card.watchedAt, locale.value, true));
162
167
 
168
+ const remainingText = computed(() => {
169
+ const left = Math.ceil((props.card.duration * (100 - progress.value)) / 100 / 60);
170
+ return t('minutes_left', { count: Math.max(left, 1) });
171
+ });
172
+
163
173
  const link = computed(() => generateLink(`/videos/${props.card.id}`));
164
174
 
165
175
  const channelLink = computed(() => props.card.channelName
@@ -325,6 +335,10 @@ $progress-height: 4px;
325
335
  flex-shrink: 0;
326
336
  }
327
337
 
338
+ .card-history__watched.--remaining {
339
+ color: $history-card-remaining;
340
+ }
341
+
328
342
  .card-history__dropdown {
329
343
  grid-row: 1/3;
330
344
  grid-column: 3;
@@ -37,7 +37,6 @@
37
37
  >
38
38
  {{ getSelectLabel(item) }}
39
39
  <UiIcon
40
- class="filter-selects__select-icon"
41
40
  name="angle-down"
42
41
  size="16"
43
42
  />
@@ -59,7 +58,6 @@
59
58
  >
60
59
  {{ item.title }}
61
60
  <UiIcon
62
- class="filter-selects__select-icon"
63
61
  name="angle-down"
64
62
  size="16"
65
63
  />
@@ -338,10 +336,6 @@ function getSelectLabel(item: IModelFilter) {
338
336
  background-color: $filters-main-select-bg--active;
339
337
  }
340
338
 
341
- .ui-btn.filter-selects__select-button .ui-icon.filter-selects__select-icon:last-child {
342
- color: $filters-main-select-icon;
343
- }
344
-
345
339
  .filter-selects__slider-popup {
346
340
  display: grid;
347
341
  row-gap: 16px;
@@ -0,0 +1,152 @@
1
+ <!-- Карточка-ссылка библиотеки: иконка-плитка, счётчик, заголовок и опциональный бейдж -->
2
+ <template>
3
+ <NuxtLink
4
+ :to="to"
5
+ class="library-item"
6
+ >
7
+ <span class="library-item__head">
8
+ <span class="library-item__icon">
9
+ <UiIcon
10
+ :name="icon"
11
+ size="20"
12
+ />
13
+ </span>
14
+ <span
15
+ v-if="subtitle"
16
+ class="library-item__subtitle _truncate"
17
+ >{{ subtitle }}</span>
18
+ </span>
19
+
20
+ <span class="library-item__footer">
21
+ <h3 class="library-item__title">{{ title }}</h3>
22
+ <span
23
+ v-if="badge"
24
+ :class="`--${badgeTheme}`"
25
+ class="library-item__badge"
26
+ >
27
+ {{ badge }}
28
+ <span
29
+ v-if="badgeNote"
30
+ class="library-item__badge-note _from-xs"
31
+ >{{ badgeNote }}</span>
32
+ </span>
33
+ </span>
34
+ </NuxtLink>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ withDefaults(defineProps<{
39
+ to: string
40
+ icon: string
41
+ title: string
42
+ /** Счётчик справа от иконки: «128 videos», «3 private · 1 public» */
43
+ subtitle?: string
44
+ /** Бейдж у заголовка: «3 new», «+214» */
45
+ badge?: string
46
+ /** Хвост бейджа, скрытый на мобиле: «this week» */
47
+ badgeNote?: string
48
+ badgeTheme?: 'accent' | 'bordered'
49
+ }>(), {
50
+ badgeTheme: 'accent'
51
+ });
52
+ </script>
53
+
54
+ <style lang="scss">
55
+ .library-item {
56
+ display: flex;
57
+ flex-direction: column;
58
+ row-gap: 32px;
59
+ justify-content: space-between;
60
+ padding: 20px;
61
+ background-color: $library-item-bg;
62
+ border: 1px solid $library-item-border;
63
+ border-radius: $radius-xl;
64
+
65
+ @include hover(background-color, border-color) {
66
+ background-color: $library-item-bg--hover;
67
+ border-color: $library-item-border--hover;
68
+ }
69
+ }
70
+
71
+ .library-item__head {
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: space-between;
75
+ column-gap: 8px;
76
+ }
77
+
78
+ .library-item__icon {
79
+ display: grid;
80
+ place-content: center;
81
+ flex-shrink: 0;
82
+ width: 44px;
83
+ height: 44px;
84
+ color: $library-item-icon;
85
+ background-color: $library-item-icon-bg;
86
+ border: 1px solid $library-item-icon-border;
87
+ border-radius: $radius-xl;
88
+
89
+ @include from-br(sm) {
90
+ transition: var(--transition) background-color, var(--transition) border-color;
91
+ }
92
+ }
93
+
94
+ @include from-br(sm) {
95
+ .library-item:is(:hover, :focus-visible) .library-item__icon {
96
+ background-color: $library-item-icon-bg--hover;
97
+ border-color: $library-item-icon-border--hover;
98
+ }
99
+ }
100
+
101
+ .library-item__subtitle {
102
+ @include font-sm;
103
+
104
+ color: $library-item-subtitle;
105
+ }
106
+
107
+ .library-item__footer {
108
+ display: flex;
109
+ align-items: center;
110
+ column-gap: 8px;
111
+ }
112
+
113
+ .library-item__title {
114
+ @include font-base;
115
+
116
+ margin: 0;
117
+ font-weight: 600;
118
+ color: $library-item-title;
119
+
120
+ @include from-br(sm) {
121
+ transition: var(--transition) color;
122
+ }
123
+ }
124
+
125
+ @include from-br(sm) {
126
+ .library-item:is(:hover, :focus-visible) .library-item__title {
127
+ color: $library-item-title--hover;
128
+ }
129
+ }
130
+
131
+ .library-item__badge {
132
+ @include font-3xs;
133
+
134
+ flex-shrink: 0;
135
+ font-weight: 600;
136
+ white-space: nowrap;
137
+ padding-block: 2px;
138
+ padding-inline: 8px;
139
+ border: 1px solid transparent;
140
+ border-radius: $radius-full;
141
+ }
142
+
143
+ .library-item__badge.--accent {
144
+ color: $library-item-badge-text;
145
+ background-color: $library-item-badge-bg;
146
+ }
147
+
148
+ .library-item__badge.--bordered {
149
+ color: $library-item-badge-bordered-text;
150
+ border-color: $library-item-badge-bordered-border;
151
+ }
152
+ </style>
@@ -122,10 +122,6 @@ const slotText = computed(() => {
122
122
  }
123
123
  }
124
124
 
125
- .ui-btn.--secondary > .ui-icon:first-child {
126
- color: var(--button-icon, #{$button-secondary-icon});
127
- }
128
-
129
125
  .ui-btn.--active.--secondary {
130
126
  background-color: var(--button-bg--active, #{$button-secondary-bg--active});
131
127
  color: var(--button-text--active, #{$button-secondary-text--active});
@@ -71,8 +71,9 @@ $toggler-size: 16px;
71
71
  right: 0;
72
72
  }
73
73
 
74
+ /* Трек уехал к правому краю лейбла — ручку считаем от него же (без слота 100% = 0). */
74
75
  .ui-toggle.--left::after {
75
- left: -1 * $control-width + $padding;
76
+ left: calc(100% - #{$control-width} + #{$padding});
76
77
  }
77
78
 
78
79
  .ui-toggle::before {
@@ -112,4 +113,9 @@ $toggler-size: 16px;
112
113
  .ui-toggle__text {
113
114
  padding-left: calc($control-width + 8px);
114
115
  }
116
+
117
+ .ui-toggle.--left .ui-toggle__text {
118
+ padding-left: 0;
119
+ padding-right: calc($control-width + 8px);
120
+ }
115
121
  </style>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.881",
4
+ "version": "0.0.885",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {
package/pages/history.vue CHANGED
@@ -225,8 +225,6 @@ const breadcrumbs = [
225
225
  }
226
226
 
227
227
  .history__clear.ui-btn {
228
- --button-icon: #{$page-history-clear-icon};
229
-
230
228
  flex-shrink: 0;
231
229
  margin-left: auto;
232
230
  }
package/pages/library.vue CHANGED
@@ -1,6 +1,366 @@
1
+ <!-- Библиотека: «продолжить просмотр», коллекции и подписки. Данные пока заглушка -->
1
2
  <template>
2
- <div class="library"/>
3
+ <div class="library _page _container">
4
+ <UiBreadcrumbs
5
+ :items="breadcrumbs"
6
+ class="_breadcrumbs"
7
+ />
8
+
9
+ <div class="library__header">
10
+ <h1 class="library__title _title">{{ t('navigation.library') }}</h1>
11
+
12
+ <UiToggle
13
+ v-model="saveHistory"
14
+ class="library__save"
15
+ left
16
+ >{{ t('history_text') }}</UiToggle>
17
+ </div>
18
+
19
+ <section
20
+ v-if="continueWatching.length > 0"
21
+ class="library__section"
22
+ >
23
+ <header class="library__section-header">
24
+ <h2 class="library__heading">
25
+ <span class="library__heading-icon">
26
+ <UiIcon
27
+ name="history"
28
+ size="16"
29
+ />
30
+ </span>
31
+ {{ t('library.continue_watching') }}
32
+ </h2>
33
+
34
+ <UiLink
35
+ :to="generateLink('/history')"
36
+ theme="secondary"
37
+ >
38
+ {{ t('library.view_all_history') }}
39
+ <UiIcon
40
+ name="arrow-right"
41
+ size="16"
42
+ />
43
+ </UiLink>
44
+ </header>
45
+
46
+ <div class="library__continue">
47
+ <CardHistory
48
+ v-for="item in continueWatching"
49
+ :key="`continue-${item.guid}`"
50
+ :card="item"
51
+ loading="eager"
52
+ priority
53
+ remaining
54
+ @share="onShare"
55
+ @remove="onRemove"
56
+ />
57
+ </div>
58
+ </section>
59
+
60
+ <section class="library__section">
61
+ <header class="library__section-header">
62
+ <h2 class="library__heading">
63
+ <span class="library__heading-icon">
64
+ <UiIcon
65
+ name="layers"
66
+ size="16"
67
+ />
68
+ </span>
69
+ {{ t('library.collections') }}
70
+ </h2>
71
+ </header>
72
+
73
+ <ul class="library__grid">
74
+ <li
75
+ v-for="item in collections"
76
+ :key="item.title"
77
+ >
78
+ <LibraryItem
79
+ :to="item.to"
80
+ :icon="item.icon"
81
+ :title="item.title"
82
+ :subtitle="item.subtitle"
83
+ />
84
+ </li>
85
+ </ul>
86
+ </section>
87
+
88
+ <section class="library__section">
89
+ <header class="library__section-header">
90
+ <h2 class="library__heading">
91
+ <span class="library__heading-icon">
92
+ <UiIcon
93
+ name="plus"
94
+ size="16"
95
+ />
96
+ </span>
97
+ {{ t('library.following') }}
98
+ </h2>
99
+
100
+ <UiBtn theme="secondary">
101
+ {{ t('library.whats_new', { count: newTotal }) }}
102
+ <UiIcon
103
+ name="arrow-right"
104
+ size="16"
105
+ />
106
+ </UiBtn>
107
+ </header>
108
+
109
+ <ul class="library__grid">
110
+ <li
111
+ v-for="item in following"
112
+ :key="item.title"
113
+ >
114
+ <LibraryItem
115
+ :to="item.to"
116
+ :icon="item.icon"
117
+ :title="item.title"
118
+ :subtitle="item.subtitle"
119
+ :badge="item.badge"
120
+ :badge-note="item.badgeNote"
121
+ :badge-theme="item.badgeTheme"
122
+ />
123
+ </li>
124
+ </ul>
125
+ </section>
126
+
127
+ <ShareMain
128
+ v-model="isShareOpen"
129
+ :url="shareUrl"
130
+ >
131
+ <template #card>
132
+ <CardVideoMini
133
+ v-if="shareCard"
134
+ :card="shareCard"
135
+ />
136
+ </template>
137
+ </ShareMain>
138
+ </div>
3
139
  </template>
4
140
 
5
141
  <script setup lang="ts">
142
+ import { getHistoryStub } from '../stubs/history';
143
+ import type { HistoryStubCard } from '../stubs/history';
144
+ import { libraryStub } from '../stubs/library';
145
+ import type { IBreadcrumbItem, IVideoCard } from '../types';
146
+
147
+ /** Карточка-ссылка библиотеки в разметке страницы. */
148
+ type LibraryLink = {
149
+ to: string
150
+ icon: string
151
+ title: string
152
+ subtitle?: string
153
+ badge?: string
154
+ badgeNote?: string
155
+ badgeTheme?: 'accent' | 'bordered'
156
+ };
157
+
158
+ const CONTINUE_LIMIT = 4;
159
+
160
+ const { t } = useI18n();
161
+
162
+ const { generateLink } = useGenerateLink();
163
+
164
+ const saveHistory = ref(true);
165
+
166
+ const removedGuids = ref<string[]>([]);
167
+
168
+ /** Недосмотренные ролики: прогресс есть, но до конца не дошли. */
169
+ const continueWatching = computed(() => getHistoryStub()
170
+ .filter(item => item.progress > 0 && item.progress < 100)
171
+ .filter(item => !removedGuids.value.includes(item.guid))
172
+ .slice(0, CONTINUE_LIMIT));
173
+
174
+ const newTotal = computed(() => libraryStub.newModels + libraryStub.newChannels + libraryStub.newPlaylists);
175
+
176
+ const collections = computed<LibraryLink[]>(() => [
177
+ {
178
+ to: generateLink('/favorites'),
179
+ icon: 'heart',
180
+ title: t('favorites'),
181
+ subtitle: t('plural.videos', libraryStub.favorites),
182
+ },
183
+ {
184
+ to: `${generateLink('/favorites')}?tab=watch-later`,
185
+ icon: 'clock',
186
+ title: t('watch_later'),
187
+ subtitle: t('plural.videos', libraryStub.watchLater),
188
+ },
189
+ {
190
+ to: generateLink('/playlists'),
191
+ icon: 'playlist',
192
+ title: t('my_playlists'),
193
+ subtitle: t('library.playlists_visibility', {
194
+ privateCount: libraryStub.privatePlaylists,
195
+ publicCount: libraryStub.publicPlaylists,
196
+ }),
197
+ },
198
+ {
199
+ to: generateLink('/history'),
200
+ icon: 'history',
201
+ title: t('history'),
202
+ subtitle: t('library.watched_today', { count: libraryStub.watchedToday }),
203
+ },
204
+ ]);
205
+
206
+ const following = computed<LibraryLink[]>(() => [
207
+ {
208
+ to: generateLink('/models'),
209
+ icon: 'user-group',
210
+ title: t('models'),
211
+ subtitle: t('plural.models', libraryStub.models),
212
+ badge: t('library.new_count', { count: libraryStub.newModels }),
213
+ },
214
+ {
215
+ to: generateLink('/channels'),
216
+ icon: 'play-circle',
217
+ title: t('channels'),
218
+ subtitle: t('plural.channels', libraryStub.channels),
219
+ badge: t('library.new_count', { count: libraryStub.newChannels }),
220
+ },
221
+ {
222
+ to: generateLink('/categories'),
223
+ icon: 'grid',
224
+ title: t('categories'),
225
+ subtitle: t('plural.categories', libraryStub.categories),
226
+ badge: `+${libraryStub.categoryVideosPerWeek}`,
227
+ badgeNote: t('library.this_week'),
228
+ badgeTheme: 'bordered',
229
+ },
230
+ {
231
+ to: generateLink('/playlists'),
232
+ icon: 'playlist',
233
+ title: t('playlists'),
234
+ subtitle: t('plural.playlists', libraryStub.playlists),
235
+ badge: t('library.new_count', { count: libraryStub.newPlaylists }),
236
+ },
237
+ ]);
238
+
239
+ function onRemove(card: HistoryStubCard) {
240
+ removedGuids.value = [...removedGuids.value, card.guid];
241
+ }
242
+
243
+ const isShareOpen = ref(false);
244
+ const shareCard = ref<IVideoCard | null>(null);
245
+
246
+ const shareUrl = computed(() => shareCard.value ? generateLink(`/videos/${shareCard.value.id}`) : '');
247
+
248
+ function onShare(card: IVideoCard) {
249
+ shareCard.value = card;
250
+ isShareOpen.value = true;
251
+ }
252
+
253
+ useNoindex();
254
+
255
+ const { brandName } = useAppConfig();
256
+
257
+ useHead(() => ({
258
+ title: `${t('navigation.library')} — ${brandName}`,
259
+ }));
260
+
261
+ const breadcrumbs = [
262
+ {
263
+ name: `${t('navigation.main')}`,
264
+ to: '/',
265
+ },
266
+ {
267
+ name: `${t('navigation.library')}`,
268
+ },
269
+ ] as IBreadcrumbItem[];
6
270
  </script>
271
+
272
+ <style lang="scss">
273
+ .library__header {
274
+ display: flex;
275
+ flex-wrap: wrap;
276
+ align-items: center;
277
+ justify-content: space-between;
278
+ column-gap: 16px;
279
+ row-gap: 12px;
280
+ margin-bottom: 24px;
281
+ }
282
+
283
+ .library__title._title {
284
+ margin-bottom: 0;
285
+ }
286
+
287
+ .library__save .ui-toggle__text {
288
+ @include font-sm;
289
+
290
+ color: $page-library-toggle-text;
291
+ }
292
+
293
+ .library__section {
294
+ margin-bottom: 48px;
295
+ }
296
+
297
+ .library__section-header {
298
+ display: flex;
299
+ flex-wrap: wrap;
300
+ align-items: center;
301
+ justify-content: space-between;
302
+ column-gap: 16px;
303
+ row-gap: 12px;
304
+ margin-bottom: 16px;
305
+ }
306
+
307
+ .library__heading {
308
+ @include font-xl;
309
+
310
+ display: flex;
311
+ align-items: center;
312
+ column-gap: 8px;
313
+ margin: 0;
314
+ font-weight: 600;
315
+ color: $page-library-heading;
316
+ }
317
+
318
+ .library__heading-icon {
319
+ display: grid;
320
+ place-content: center;
321
+ flex-shrink: 0;
322
+ width: 24px;
323
+ height: 24px;
324
+ color: $page-library-heading-icon;
325
+ background-color: $page-library-heading-icon-bg;
326
+ border-radius: $radius-md;
327
+ }
328
+
329
+ .library__continue {
330
+ display: grid;
331
+ gap: 16px;
332
+ grid-template-columns: 1fr;
333
+
334
+ @include from-br(xm) {
335
+ grid-template-columns: repeat(2, 1fr);
336
+ }
337
+
338
+ @include from-br(sm) {
339
+ gap: 24px;
340
+ grid-template-columns: repeat(3, 1fr);
341
+ }
342
+
343
+ @include from-br(md) {
344
+ grid-template-columns: repeat(4, 1fr);
345
+ }
346
+ }
347
+
348
+ .library__grid {
349
+ display: grid;
350
+ gap: 16px;
351
+ margin: 0;
352
+ padding: 0;
353
+ list-style: none;
354
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
355
+
356
+ @include from-br(sm) {
357
+ gap: 24px;
358
+ grid-template-columns: repeat(4, 1fr);
359
+ }
360
+ }
361
+
362
+ /* Карточка тянется на всю высоту ряда — иначе соседи с двухстрочным тайтлом разной высоты. */
363
+ .library__grid > li {
364
+ display: grid;
365
+ }
366
+ </style>
@@ -14,7 +14,7 @@ const cleanTimelineGroups = (raw: ITimelineGroup[] | null): ISceneGroup[] | null
14
14
  .filter((group) => Array.isArray(group?.actions) && group.actions.length > 0)
15
15
  .map((group) => ({
16
16
  id: group.name || '',
17
- title: humanize(group.name),
17
+ title: humanize(group.title),
18
18
  scenes: group.actions
19
19
  .filter((action) => action?.title)
20
20
  .map((action) => ({
@@ -0,0 +1,16 @@
1
+ /** Счётчики карточек «Библиотеки» — ручек под них пока нет, вёрстка живёт на заглушке. */
2
+ export const libraryStub = {
3
+ favorites: 128,
4
+ watchLater: 12,
5
+ privatePlaylists: 3,
6
+ publicPlaylists: 1,
7
+ watchedToday: 6,
8
+ models: 12,
9
+ newModels: 3,
10
+ channels: 5,
11
+ newChannels: 1,
12
+ categories: 4,
13
+ categoryVideosPerWeek: 214,
14
+ playlists: 7,
15
+ newPlaylists: 2,
16
+ };
@@ -13,6 +13,7 @@ export interface ITimelineGroupAction {
13
13
 
14
14
  export interface ITimelineGroup {
15
15
  name: string;
16
+ title: string;
16
17
  actions: ITimelineGroupAction[];
17
18
  }
18
19