itube-specs 0.0.865 → 0.0.868
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/README.md +7 -4
- package/assets/scss/themes/dark/_theme.scss +12 -0
- package/assets/scss/themes/light/_theme.scss +12 -0
- package/components/layout/burger.vue +9 -0
- package/components/layout/header.vue +15 -3
- package/components/layout/niche.vue +179 -0
- package/composables/use-niches.ts +47 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,13 +63,15 @@ npm run lint:css # Stylelint по **/*.{scss,vue}
|
|
|
63
63
|
npm run lint:css:fix # Stylelint --fix
|
|
64
64
|
npm run check:themes # сверяет наборы имён токенов во всех темах
|
|
65
65
|
npm run check:styles # компилирует стили каждого SFC каждой темой (~1.5s)
|
|
66
|
+
npm run check:breakpoints # сверяет JS-зеркало брейкпоинтов с $brkpnts в SCSS
|
|
66
67
|
npm run test # vitest однократно
|
|
67
68
|
npm run patch # npm version patch — бамп версии перед публикацией
|
|
68
69
|
```
|
|
69
70
|
|
|
70
71
|
Pre-commit (husky + lint-staged): на закоммиченных файлах прогоняются `eslint --fix` и `stylelint --fix`.
|
|
71
|
-
Если в коммите есть `assets/scss/themes/**`, гоняется `check:themes`; если любой `.vue` или
|
|
72
|
-
`check:styles`.
|
|
72
|
+
Если в коммите есть `assets/scss/themes/**`, гоняется `check:themes`; если любой `.vue` или
|
|
73
|
+
`assets/scss/**` — `check:styles`; если пара файлов брейкпоинтов — `check:breakpoints`. Все три есть и в
|
|
74
|
+
CI, в шаге Lint.
|
|
73
75
|
⚠️ **Шаблоны `.vue` не типизируются вообще.** Обычный `tsc` их не читает, а `vue-tsc` не подключён (накопился
|
|
74
76
|
бэклог ошибок в шаблонах, подключение — отдельная задача). Значит чистый `tsc` ничего не говорит про выражения
|
|
75
77
|
в шаблоне: арифметика над строковым union, неверный тип пропа, переименованное поле пройдут молча.
|
|
@@ -87,7 +89,8 @@ runtime/ # runtime-значения и хелперы (barrel index.ts)
|
|
|
87
89
|
constants/ # доменные `as const` (Niche, PlaylistType, AdSpotType, …)
|
|
88
90
|
utils/ # чистые хелперы, cleaners/, converters/
|
|
89
91
|
types/ # .d.ts типы (barrel index.d.ts — с расширением .d.ts в реэкспортах)
|
|
90
|
-
lib/ # статические
|
|
92
|
+
lib/ # продуктовые статические данные (barrel index.ts): scheme/*, *-items, *-scheme
|
|
93
|
+
config/ # build-time конфиг для приложения (barrel index.ts): themes, css-breakpoints
|
|
91
94
|
middleware/ # глобальные: auth.global, normalize.global, age.global
|
|
92
95
|
plugins/ # SSR/клиент: country/countries/mobile (SSR), services.client, sentry.client, adv
|
|
93
96
|
server/ # BFF: api/** (тонкие прокси к внешнему API), utils/metrics, plugins/*, tasks
|
|
@@ -173,7 +176,7 @@ ad-конфиг), теперь читается из `runtimeConfig.public` (з
|
|
|
173
176
|
## Что живёт в слое, а что в приложении
|
|
174
177
|
|
|
175
178
|
- **Слой:** компоненты, composables, API-сервисы, доменные утилиты, runtime-константы/хелперы, типы,
|
|
176
|
-
`lib/` (
|
|
179
|
+
`lib/` (продуктовые данные), `config/` (темы, брейкпоинты), middleware (`auth`/`normalize`/`age`), SSR-плагины
|
|
177
180
|
(`country`/`mobile`/`services`/`sentry`/`adv`), BFF-хендлеры `server/api/**` — всё site-agnostic.
|
|
178
181
|
- **Приложение:** только per-site — **значения** config (флаги/реклама/локали) через `runtimeConfig.public`
|
|
179
182
|
(слой держит пустые контейнеры-заглушки), i18n-локали, `public/` и **выбор темы** (`theme:` в
|
|
@@ -338,6 +338,18 @@ $language-value-text: $color-white;
|
|
|
338
338
|
$language-description: $color-zinc-500;
|
|
339
339
|
$language-empty-text: $color-zinc-300;
|
|
340
340
|
|
|
341
|
+
// ─── Niche ───
|
|
342
|
+
$niche-trigger-text: $color-zinc-400;
|
|
343
|
+
$niche-angle--hover: $color-white;
|
|
344
|
+
$niche-title: $color-zinc-500;
|
|
345
|
+
$niche-value-text: $color-white;
|
|
346
|
+
$niche-menu-bg: $color-night;
|
|
347
|
+
$niche-menu-border: $color-zinc-800;
|
|
348
|
+
$niche-item-text: $color-zinc-300;
|
|
349
|
+
$niche-item-bg--hover: $color-zinc-800;
|
|
350
|
+
$niche-item-text--current: $color-white;
|
|
351
|
+
$niche-item-bg--current: $color-zinc-700;
|
|
352
|
+
|
|
341
353
|
// ─── Footer language ───
|
|
342
354
|
$footer-language-bg: $color-zinc-800-50;
|
|
343
355
|
$footer-language-bg--hover: $color-zinc-700;
|
|
@@ -395,6 +395,18 @@ $language-value-text: $color-white;
|
|
|
395
395
|
$language-description: $color-gray-500;
|
|
396
396
|
$language-empty-text: $color-gray-400;
|
|
397
397
|
|
|
398
|
+
// ─── Niche ───
|
|
399
|
+
$niche-trigger-text: $color-gray-500;
|
|
400
|
+
$niche-angle--hover: $color-gray-300;
|
|
401
|
+
$niche-title: $color-gray-400;
|
|
402
|
+
$niche-value-text: $color-white;
|
|
403
|
+
$niche-menu-bg: $color-slate-850;
|
|
404
|
+
$niche-menu-border: $color-white-10;
|
|
405
|
+
$niche-item-text: $color-gray-300;
|
|
406
|
+
$niche-item-bg--hover: $color-slate-750;
|
|
407
|
+
$niche-item-text--current: $color-white;
|
|
408
|
+
$niche-item-bg--current: $color-indigo-soft;
|
|
409
|
+
|
|
398
410
|
// ─── Footer language ───
|
|
399
411
|
$footer-language-bg: $color-slate-750;
|
|
400
412
|
$footer-language-bg--hover: $color-slate-700;
|
|
@@ -68,6 +68,11 @@
|
|
|
68
68
|
</div>
|
|
69
69
|
</nav>
|
|
70
70
|
|
|
71
|
+
<LayoutNiche
|
|
72
|
+
class="layout-burger__niche"
|
|
73
|
+
icon-size="20"
|
|
74
|
+
/>
|
|
75
|
+
|
|
71
76
|
<LayoutLanguage
|
|
72
77
|
class="layout-burger__language"
|
|
73
78
|
icon-size="20"
|
|
@@ -257,6 +262,10 @@ router.afterEach(() => {
|
|
|
257
262
|
padding-bottom: 12px;
|
|
258
263
|
}
|
|
259
264
|
|
|
265
|
+
.layout-burger__niche {
|
|
266
|
+
margin-bottom: 12px;
|
|
267
|
+
}
|
|
268
|
+
|
|
260
269
|
.layout-burger__close {
|
|
261
270
|
position: fixed;
|
|
262
271
|
top: 16px;
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
glow
|
|
11
11
|
class="layout-header__logo"
|
|
12
12
|
/>
|
|
13
|
+
<LayoutNiche
|
|
14
|
+
class="layout-header__niche _from-sm"
|
|
15
|
+
icon-size="20"
|
|
16
|
+
/>
|
|
17
|
+
|
|
13
18
|
<div
|
|
14
19
|
ref="languageRef"
|
|
15
20
|
class="layout-header__language _from-sm"
|
|
@@ -135,7 +140,7 @@ $breakpoint: sm;
|
|
|
135
140
|
width: 100%;
|
|
136
141
|
|
|
137
142
|
@include from-br(sm) {
|
|
138
|
-
grid-template-columns: auto 1fr auto auto;
|
|
143
|
+
grid-template-columns: auto 1fr auto auto auto;
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
146
|
|
|
@@ -180,7 +185,14 @@ $breakpoint: sm;
|
|
|
180
185
|
.layout-header__user,
|
|
181
186
|
.layout-header__sign-up {
|
|
182
187
|
@include from-br(sm) {
|
|
183
|
-
grid-column:
|
|
188
|
+
grid-column: 5;
|
|
189
|
+
grid-row: 1;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.layout-header__niche {
|
|
194
|
+
@include from-br(sm) {
|
|
195
|
+
grid-column: 3;
|
|
184
196
|
grid-row: 1;
|
|
185
197
|
}
|
|
186
198
|
}
|
|
@@ -194,7 +206,7 @@ $breakpoint: sm;
|
|
|
194
206
|
.layout-header__language {
|
|
195
207
|
@include from-br(sm) {
|
|
196
208
|
position: relative;
|
|
197
|
-
grid-column:
|
|
209
|
+
grid-column: 4;
|
|
198
210
|
grid-row: 1;
|
|
199
211
|
}
|
|
200
212
|
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="niches.length > 1"
|
|
4
|
+
ref="nicheRef"
|
|
5
|
+
class="layout-niche"
|
|
6
|
+
@keydown.esc="isOpen = false"
|
|
7
|
+
>
|
|
8
|
+
<button
|
|
9
|
+
:class="{'--open': isOpen}"
|
|
10
|
+
:aria-expanded="isOpen"
|
|
11
|
+
:aria-label="$t('niche')"
|
|
12
|
+
class="layout-niche__trigger"
|
|
13
|
+
type="button"
|
|
14
|
+
@click="isOpen = !isOpen"
|
|
15
|
+
>
|
|
16
|
+
<UiIcon
|
|
17
|
+
:name="currentNiche.icon"
|
|
18
|
+
:size="iconSize"
|
|
19
|
+
class="layout-niche__icon"
|
|
20
|
+
/>
|
|
21
|
+
<span class="layout-niche__current _to-sm">
|
|
22
|
+
<span>{{ $t('niche') }}: </span>
|
|
23
|
+
<span class="layout-niche__text">{{ $t(currentNiche.label) }}</span>
|
|
24
|
+
</span>
|
|
25
|
+
<UiIcon
|
|
26
|
+
class="layout-niche__angle"
|
|
27
|
+
name="angle-down"
|
|
28
|
+
size="12"
|
|
29
|
+
/>
|
|
30
|
+
</button>
|
|
31
|
+
<ul
|
|
32
|
+
v-if="isOpen"
|
|
33
|
+
class="layout-niche__list"
|
|
34
|
+
>
|
|
35
|
+
<li
|
|
36
|
+
v-for="item in niches"
|
|
37
|
+
:key="`niche-${item.name}`"
|
|
38
|
+
>
|
|
39
|
+
<button
|
|
40
|
+
:class="{'--current': item.name === currentNiche.name}"
|
|
41
|
+
:aria-current="item.name === currentNiche.name ? 'true' : undefined"
|
|
42
|
+
class="layout-niche__item"
|
|
43
|
+
type="button"
|
|
44
|
+
@click="select(item.name)"
|
|
45
|
+
>
|
|
46
|
+
<UiIcon
|
|
47
|
+
:name="item.icon"
|
|
48
|
+
class="layout-niche__item-icon"
|
|
49
|
+
size="20"
|
|
50
|
+
/>
|
|
51
|
+
{{ $t(item.label) }}
|
|
52
|
+
</button>
|
|
53
|
+
</li>
|
|
54
|
+
</ul>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<script setup lang="ts">
|
|
59
|
+
import { onClickOutside } from '@vueuse/core';
|
|
60
|
+
import type { Niche } from '../../runtime';
|
|
61
|
+
|
|
62
|
+
withDefaults(defineProps<{
|
|
63
|
+
iconSize?: string
|
|
64
|
+
}>(), {
|
|
65
|
+
iconSize: '20'
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const { niches, currentNiche, changeNiche } = useNiches();
|
|
70
|
+
|
|
71
|
+
const nicheRef = ref<HTMLElement>();
|
|
72
|
+
const isOpen = ref(false);
|
|
73
|
+
|
|
74
|
+
onClickOutside(nicheRef, () => {
|
|
75
|
+
isOpen.value = false;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function select(name: Niche) {
|
|
79
|
+
isOpen.value = false;
|
|
80
|
+
changeNiche(name);
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<style lang="scss">
|
|
85
|
+
.layout-niche {
|
|
86
|
+
display: grid;
|
|
87
|
+
|
|
88
|
+
@include from-br(sm) {
|
|
89
|
+
position: relative;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.layout-niche__trigger {
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-auto-flow: column;
|
|
96
|
+
align-items: center;
|
|
97
|
+
justify-content: start;
|
|
98
|
+
column-gap: 6px;
|
|
99
|
+
padding: 6px 8px;
|
|
100
|
+
color: $niche-trigger-text;
|
|
101
|
+
|
|
102
|
+
@include to-br(sm) {
|
|
103
|
+
grid-template-columns: auto 1fr auto;
|
|
104
|
+
width: 100%;
|
|
105
|
+
padding: 10px 16px;
|
|
106
|
+
column-gap: 16px;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.layout-niche__current {
|
|
111
|
+
@include font-sm;
|
|
112
|
+
|
|
113
|
+
display: grid;
|
|
114
|
+
grid-template-columns: auto 1fr;
|
|
115
|
+
color: $niche-title;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.layout-niche__text {
|
|
119
|
+
color: $niche-value-text;
|
|
120
|
+
font-weight: 500;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.layout-niche__angle {
|
|
124
|
+
pointer-events: none;
|
|
125
|
+
transition: transform var(--transition), color var(--transition);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.layout-niche__trigger:hover .layout-niche__angle {
|
|
129
|
+
color: $niche-angle--hover;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.layout-niche__trigger.--open .layout-niche__angle {
|
|
133
|
+
transform: rotate(180deg);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.layout-niche__list {
|
|
137
|
+
display: grid;
|
|
138
|
+
|
|
139
|
+
@include from-br(sm) {
|
|
140
|
+
position: absolute;
|
|
141
|
+
right: 0;
|
|
142
|
+
bottom: -8px;
|
|
143
|
+
z-index: $z-index-plus;
|
|
144
|
+
min-width: 180px;
|
|
145
|
+
padding: 8px;
|
|
146
|
+
background-color: $niche-menu-bg;
|
|
147
|
+
border: 1px solid $niche-menu-border;
|
|
148
|
+
border-radius: $radius-xl;
|
|
149
|
+
transform: translateY(100%);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.layout-niche__item {
|
|
154
|
+
@include font-sm;
|
|
155
|
+
|
|
156
|
+
display: grid;
|
|
157
|
+
grid-template-columns: 20px 1fr;
|
|
158
|
+
align-items: center;
|
|
159
|
+
column-gap: 12px;
|
|
160
|
+
width: 100%;
|
|
161
|
+
padding: 10px 16px;
|
|
162
|
+
color: $niche-item-text;
|
|
163
|
+
text-align: start;
|
|
164
|
+
border-radius: $radius-lg;
|
|
165
|
+
|
|
166
|
+
@include hover(background-color) {
|
|
167
|
+
background-color: $niche-item-bg--hover;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@include from-br(sm) {
|
|
171
|
+
padding: 8px 10px;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.layout-niche__item.--current {
|
|
176
|
+
color: $niche-item-text--current;
|
|
177
|
+
background-color: $niche-item-bg--current;
|
|
178
|
+
}
|
|
179
|
+
</style>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Niche } from '../runtime';
|
|
2
|
+
import { NicheHelper } from '../utils/niche-helper';
|
|
3
|
+
|
|
4
|
+
/** Иконка из assets/icons/common под каждую нишу. */
|
|
5
|
+
const NICHE_ICONS: Record<Niche, string> = {
|
|
6
|
+
straight: 'hetero',
|
|
7
|
+
gay: 'gay',
|
|
8
|
+
shemale: 'shemale',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export interface INicheItem {
|
|
12
|
+
name: Niche,
|
|
13
|
+
icon: string,
|
|
14
|
+
/** Ключ i18n с названием ниши */
|
|
15
|
+
label: string,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Ниши сайта для переключателя: список из per-site конфига проекта (appConfig.niches),
|
|
20
|
+
* текущая — из useState('niche') с фолбэком на дефолтную нишу окружения.
|
|
21
|
+
*/
|
|
22
|
+
export const useNiches = () => {
|
|
23
|
+
const defaultNiche = NicheHelper.getSiteDefaultNiche();
|
|
24
|
+
const current = useState<Niche>('niche', () => defaultNiche);
|
|
25
|
+
const localePath = useLocalePath();
|
|
26
|
+
|
|
27
|
+
const niches = computed<INicheItem[]>(() =>
|
|
28
|
+
((useAppConfig().niches as Niche[]) || []).map(name => ({
|
|
29
|
+
name,
|
|
30
|
+
icon: NICHE_ICONS[name] || 'earth',
|
|
31
|
+
label: `niches.${name}`,
|
|
32
|
+
})));
|
|
33
|
+
|
|
34
|
+
const currentNiche = computed(() => niches.value.find(item => item.name === current.value) || niches.value[0]);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Ниша влияет и на роутинг (префикс пути), и на заголовок X-Niche в SSR-запросах,
|
|
38
|
+
* поэтому переключение — полной перезагрузкой на корень выбранной ниши.
|
|
39
|
+
*/
|
|
40
|
+
function changeNiche(name: Niche) {
|
|
41
|
+
if (name === current.value) return;
|
|
42
|
+
current.value = name;
|
|
43
|
+
window.location.href = localePath(name === defaultNiche ? '/' : `/${name}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { niches, currentNiche, changeNiche };
|
|
47
|
+
};
|