itube-specs 0.0.921 → 0.0.923
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/DOCS/CONVENTIONS.md +44 -0
- package/DOCS/STYLES.md +127 -0
- package/README.md +6 -0
- package/assets/scss/base/_global.scss +3 -0
- package/assets/scss/base/_loading.scss +1 -0
- package/assets/scss/base/_utils.scss +10 -0
- package/assets/scss/layout/_page.scss +8 -0
- package/assets/scss/mixins/_breakpoints.scss +6 -3
- package/assets/scss/mixins/_font-sizes.scss +12 -0
- package/assets/scss/mixins/_page.scss +5 -0
- package/assets/scss/mixins/_video-card.scss +2 -0
- package/package.json +3 -2
- package/server/middleware/x-domain.ts +19 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Conventions
|
|
2
|
+
|
|
3
|
+
Layer + every site. From an app: `node_modules/itube-specs/DOCS/CONVENTIONS.md`. Styles → `DOCS/STYLES.md`.
|
|
4
|
+
Human-facing rationale → the layer's `README.md` (Russian).
|
|
5
|
+
|
|
6
|
+
## Components
|
|
7
|
+
|
|
8
|
+
`pathPrefix: true` → name = folder path + filename, repeated segments deduped by Nuxt. Folder is the
|
|
9
|
+
namespace; filename must not repeat it.
|
|
10
|
+
|
|
11
|
+
- `ui/icon.vue` → `<UiIcon>` · `player/core.vue` → `<PlayerCore>` · `info/grid.vue` → `<InfoGrid>` (not
|
|
12
|
+
`<InfoInfoGrid>`)
|
|
13
|
+
- Folder root: `index.vue` when the deduped name is already multi-word (`card/video/index.vue` →
|
|
14
|
+
`<CardVideo>`); `main.vue` only when `index.vue` would give one word (`filter/main.vue` → `<FilterMain>`).
|
|
15
|
+
Never a single-word name — clashes with HTML elements.
|
|
16
|
+
- BEM root class = kebab-case of the name: `<CardVideo>` → `.card-video`
|
|
17
|
+
- `pathPrefix: true` mandatory — layers break component exports under `false` (nuxt/nuxt#30099)
|
|
18
|
+
|
|
19
|
+
**App override:** an app defining the same name outranks the layer and wins auto-import; callers untouched.
|
|
20
|
+
Don't fork a component when a prop expresses the difference.
|
|
21
|
+
|
|
22
|
+
## Markup
|
|
23
|
+
|
|
24
|
+
`<div>` is the fallback, not the default.
|
|
25
|
+
|
|
26
|
+
| Need | Tag |
|
|
27
|
+
|---|---|
|
|
28
|
+
| text | `<span>` inline, `<p>` paragraph — never bare `<div>` |
|
|
29
|
+
| structure | `header`, `nav`, `main`, `aside`, `footer`, `section`, `article` |
|
|
30
|
+
| sequence of similar items | `<ul>`/`<ol>` + `<li>` — chip rows and tag clouds included |
|
|
31
|
+
| action / navigation / collapsible | `<button>` / `<a>` / `<details>`+`<summary>` |
|
|
32
|
+
| forms | `<label>`, `<fieldset>`, `<legend>`, native `<input>` types |
|
|
33
|
+
| emphasis, figures | `<em>`/`<strong>`, `<figure>`/`<figcaption>` — not style-only spans |
|
|
34
|
+
|
|
35
|
+
## TypeScript
|
|
36
|
+
|
|
37
|
+
- Semicolons always. No code comments unless asked.
|
|
38
|
+
- Domain string sets: `as const` PascalCase objects in `runtime/constants/`, type via
|
|
39
|
+
`typeof X[keyof typeof X]`. Never `enum` — it survives into the bundle, and `import type` erases it so
|
|
40
|
+
`X.Member` is `undefined` at runtime in a published layer; you also can't assign a raw API/URL/cookie string
|
|
41
|
+
to an enum. String values, not numeric (`0` is falsy — `if (spotType)` misreads member `0` as absent;
|
|
42
|
+
`AdSpotType` is numeric, a wart, don't copy). Framework unions (Nuxt status) stay unions.
|
|
43
|
+
- `.vue` templates are NOT typechecked — `tsc` doesn't read them, `vue-tsc` isn't a gate. A clean `tsc` says
|
|
44
|
+
nothing about template expressions. Read them, or `npx vue-tsc --noEmit` ad hoc past pre-existing noise.
|
package/DOCS/STYLES.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Styles
|
|
2
|
+
|
|
3
|
+
Canonical for every itube site — the layer owns 100% of the SCSS; apps have an empty `assets/scss/` override
|
|
4
|
+
slot. Read before touching any `<style lang="scss">`, `assets/scss/**`, or a theme token. Paths are
|
|
5
|
+
layer-relative; from an app: `node_modules/itube-specs/DOCS/STYLES.md`. Human-facing version → `README.md`.
|
|
6
|
+
|
|
7
|
+
## Co-location
|
|
8
|
+
|
|
9
|
+
- Component and page styles in the SFC's own `<style lang="scss">`. **No `scoped`** — flat BEM isolates.
|
|
10
|
+
- Nothing per-component or per-page in `assets/scss/`.
|
|
11
|
+
- Tree: `assets/scss/` → `vars/` (palette, vars, tokens, z-index), `themes/`, `mixins/`, `base/`, `layout/`,
|
|
12
|
+
`main.scss`. `css` entry is `main.scss`, loads only `vars/tokens`, `base`, `layout`.
|
|
13
|
+
- **Never `@use 'vars'`/`'mixins'` in an SFC block** — auto-injected by the *app's*
|
|
14
|
+
`vite.css.preprocessorOptions.scss.additionalData` + `loadPaths`.
|
|
15
|
+
- Sass-loaded global partials: `@use` relatively within their own root, bare specifiers across roots
|
|
16
|
+
(layer↔app) so `loadPaths` resolves.
|
|
17
|
+
- The layer has no `sass` package — layer styles compile only inside an app, never in the layer's vitest.
|
|
18
|
+
|
|
19
|
+
## Themes
|
|
20
|
+
|
|
21
|
+
Dir per theme: `assets/scss/themes/<name>/_theme.scss`. App picks one via `theme:` in its
|
|
22
|
+
`config/site.config.ts` (read by its `nuxt.config.ts` *and* `app.config.ts` — `useAppConfig()` doesn't exist at
|
|
23
|
+
config time), appends `themes/<theme>/` to `loadPaths`; `@forward 'theme'` in `vars/_index.scss` resolves
|
|
24
|
+
there because no `_theme.scss` sits beside it.
|
|
25
|
+
|
|
26
|
+
**Dir-per-theme is load-bearing** — Sass needs a literal `@use`/`@forward` path, so only the folder can vary.
|
|
27
|
+
|
|
28
|
+
`loadPaths = [<app>/assets/scss, <layer>/assets/scss/themes/<theme>, <layer>/assets/scss]`. App first → a site
|
|
29
|
+
overrides any partial by name (its `vars/_theme.scss` wins over loadPaths).
|
|
30
|
+
|
|
31
|
+
**Every token must exist in every theme** — a missing one is a hard Sass error that kills the app build. Two
|
|
32
|
+
guards, both pre-commit + CI:
|
|
33
|
+
|
|
34
|
+
| Script | Proves | Blind spot |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `check:themes` | themes declare the same top-level `$name:` set (`$surfaces` / `$button-variant-overrides` contents ignored — a theme may leave either empty) | token declared in **no** theme |
|
|
37
|
+
| `check:styles` | every `<style>` block compiles against every theme, same preamble + loadPaths as Vite; names the failing theme (~1.5s) | — |
|
|
38
|
+
|
|
39
|
+
## BEM
|
|
40
|
+
|
|
41
|
+
`.block {}`, `.block__element {}`, `.block.--modifier {}` — modifier prefix `--`.
|
|
42
|
+
|
|
43
|
+
**No nesting**, every selector flat with its full class name. `&` only for pseudo-classes, pseudo-elements,
|
|
44
|
+
state modifiers (`&::after`, `&:focus-visible`, `&.--open`).
|
|
45
|
+
|
|
46
|
+
```scss
|
|
47
|
+
.layout-header {}
|
|
48
|
+
.layout-header__logo {} // not .layout-header { &__logo {} }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Hover
|
|
52
|
+
|
|
53
|
+
**Always `@include hover(<properties>)`, never `&:hover`.** Wraps in `@media (hover: hover)` (on touch a hover
|
|
54
|
+
sticks after a tap), declares `--transition` for the listed properties, pairs `:hover` with `:focus-visible`.
|
|
55
|
+
|
|
56
|
+
```scss
|
|
57
|
+
.ui-btn.--muted {
|
|
58
|
+
@include hover(background-color, border) { background-color: $button-muted-bg--hover; }
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Applies to the current selector only, so hovering one element to restyle another is written flat inside your
|
|
63
|
+
own media query:
|
|
64
|
+
|
|
65
|
+
```scss
|
|
66
|
+
@media (hover: hover) {
|
|
67
|
+
.card-round:is(:hover, :focus-visible) .card-round__img { transform: scale(1.05); }
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Tokens
|
|
72
|
+
|
|
73
|
+
Never hardcode a color, size or z-index.
|
|
74
|
+
|
|
75
|
+
| File | Holds | Type |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `vars/_palette.scss` | `$color-indigo-600` — raw palette | SCSS var |
|
|
78
|
+
| `themes/<theme>/_theme.scss` | `$chips-text`, `$button-bg--hover` — colors, one per theme | SCSS var |
|
|
79
|
+
| `vars/_vars.scss` | `$radius-*`, `$blur-*`, `$spacing` — static non-color | SCSS var |
|
|
80
|
+
| `vars/_tokens.scss` | `--header-height` — changes at a breakpoint or set via JS | CSS custom prop |
|
|
81
|
+
|
|
82
|
+
- Changes at a breakpoint or set via JS → CSS custom property. Everything else → SCSS var.
|
|
83
|
+
- **Palette is private**, consumed only by theme files. A component must never use `$color-*` — add the token
|
|
84
|
+
to **every** theme first.
|
|
85
|
+
- **No semantic tier** (`$text-muted`, `$role-border-subtle`) — themes reference palette directly.
|
|
86
|
+
- Theme files hold **colors only**; radius/blur/static → `vars/_vars.scss`.
|
|
87
|
+
|
|
88
|
+
### Context-dependent colors
|
|
89
|
+
|
|
90
|
+
Never a theme flag in a component. Two mixins, driven from the theme file:
|
|
91
|
+
|
|
92
|
+
- `surface($name)` — block declares itself an island (`.ui-popup { @include surface(overlay); }`), contents
|
|
93
|
+
pick up colors by cascade. Theme supplies deltas via `$surfaces`; no differentiation → `$surfaces: ()`.
|
|
94
|
+
Components read `var(--input-bg, #{$input-bg})`, so a single color can still be overridden directly.
|
|
95
|
+
- `button-variant($key)` — place asks, theme answers by name via `$button-variant-overrides`. The element's
|
|
96
|
+
class still reflects the `theme=` prop, so the mixin prints `--button-variant` as a DevTools marker.
|
|
97
|
+
|
|
98
|
+
`appConfig.theme` — structural differences only (different element, extra icon, `v-if`).
|
|
99
|
+
|
|
100
|
+
### Naming
|
|
101
|
+
|
|
102
|
+
`$<block>-<element>?-<modifier>?-<property>-<state>?`
|
|
103
|
+
|
|
104
|
+
- block = component root · element = BEM element, optional · modifier = variant, optional
|
|
105
|
+
- property — always present, always short: `bg`, `text`, `border`, `radius`, `icon`, `shadow`. Never the full
|
|
106
|
+
CSS name (`$button-background` → `$button-bg`, `$input-border-radius` → `$input-radius`). `color` is banned
|
|
107
|
+
as a property — ambiguous, use `text`.
|
|
108
|
+
- state — last, after `--`: `hover`, `active`, `focus`, `disabled`, `checked`, `current`, `error`
|
|
109
|
+
|
|
110
|
+
State goes last even when it reads worse: `$video-playlist-card-bg--active`, NOT
|
|
111
|
+
`$video-playlist-card-active-bg`. A state-looking word can be a sub-element: `$filter-popup-active-chip-bg` is
|
|
112
|
+
`active-chip`.
|
|
113
|
+
|
|
114
|
+
Text-only elements (`title`, `name`, `subtext`, `description`, `label`, `count`) may drop `-text`:
|
|
115
|
+
`$video-card-title`.
|
|
116
|
+
|
|
117
|
+
## Typography and breakpoints
|
|
118
|
+
|
|
119
|
+
- Type: `@include font-sm` (14/20), `font-xs` (12/16), `font-base` (16/24) — never raw
|
|
120
|
+
`font-size`/`line-height`.
|
|
121
|
+
- Breakpoints: `@include from-br(sm)`, `to-br(xs)`. Container: `from-cq($bp)`. JS mirror of `$brkpnts` in
|
|
122
|
+
`config/`, guarded by `check:breakpoints`.
|
|
123
|
+
|
|
124
|
+
**All mixins** (auto-injected, never `@use`): `font-4xs|3xs|xs|2xs|sm|md|base|lg|xl|2xl|3xl|4xl`,
|
|
125
|
+
`from-br|to-br`, `from-cq|to-cq`, `hover`, `surface`, `button-variant`, `page|page-title|page-footer-links`,
|
|
126
|
+
`video-card|video-card-grid|video-card-poster`, `area-point`. Signatures — SCSS-mixins table in the app's
|
|
127
|
+
`DOCS/CATALOG.md`.
|
package/README.md
CHANGED
|
@@ -143,6 +143,12 @@ assets/scss/themes/dark/_theme.scss
|
|
|
143
143
|
подставляет приложение, и компилирует по разу на тему. То же, что делает Vite, — значит ловит и неизвестные
|
|
144
144
|
миксины, и неверные аргументы `@include`, и показывает, в какой теме падает.
|
|
145
145
|
|
|
146
|
+
**Промежуточного семантического слоя нет** — темы ссылаются на палитру напрямую, без тира вида
|
|
147
|
+
`$text-muted` / `$role-border-subtle`. Сайты форкаются с принципиально разными схемами, и каждому
|
|
148
|
+
селектору может понадобиться произвольный цвет, а не одна из горстки ролей. Такой слой либо оброс бы
|
|
149
|
+
ролью на селектор (и перестал быть семантическим), либо компоненты начали бы тянуться в обход него.
|
|
150
|
+
Имя токена по компоненту, который он обслуживает, держит соответствие честным.
|
|
151
|
+
|
|
146
152
|
Контекстные различия (контрол внутри попапа, вариант кнопки, зависящий от темы) решают миксины
|
|
147
153
|
`surface()` и `button-variant()` из `mixins/`: место объявляет себя, а значения даёт тема через карты
|
|
148
154
|
`$surfaces` / `$button-variant-overrides`. Компоненты про темы не знают. `appConfig.theme` — только
|
|
@@ -82,15 +82,18 @@ summary::-webkit-details-marker {
|
|
|
82
82
|
clip-path: inset(100%);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// Убрать элемент из потока целиком, в отличие от ._visually-hidden.
|
|
85
86
|
._hidden {
|
|
86
87
|
display: none !important;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
@for $i from 1 through 12 {
|
|
91
|
+
// Отбивка снизу: чётные значения 2…24px (._mb-8 = margin-bottom: 8px).
|
|
90
92
|
._mb-#{$i * 2} {
|
|
91
93
|
margin-bottom: #{$i * 2}px;
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
// Отбивка сверху: чётные значения 2…24px (._mt-8 = margin-top: 8px).
|
|
94
97
|
._mt-#{$i * 2} {
|
|
95
98
|
margin-top: #{$i * 2}px;
|
|
96
99
|
}
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
@use '../mixins/breakpoints' as *;
|
|
2
2
|
|
|
3
|
+
// Виден только ниже xs (475px), на xs и шире скрыт.
|
|
3
4
|
@include from-br(xs) { ._to-xs { display: none !important; } }
|
|
4
5
|
|
|
6
|
+
// Виден только ниже sm (960px), на sm и шире скрыт.
|
|
5
7
|
@include from-br(sm) { ._to-sm { display: none !important; } }
|
|
6
8
|
|
|
9
|
+
// Виден только ниже md (1350px), на md и шире скрыт.
|
|
7
10
|
@include from-br(md) { ._to-md { display: none !important; } }
|
|
8
11
|
|
|
12
|
+
// Виден только ниже lg (1792px), на lg и шире скрыт.
|
|
9
13
|
@include from-br(lg) { ._to-lg { display: none !important; } }
|
|
10
14
|
|
|
15
|
+
// Виден с xs (475px) и шире, ниже скрыт.
|
|
11
16
|
@include to-br(xs) { ._from-xs { display: none !important; } }
|
|
12
17
|
|
|
18
|
+
// Виден с sm (960px) и шире, ниже скрыт.
|
|
13
19
|
@include to-br(sm) { ._from-sm { display: none !important; } }
|
|
14
20
|
|
|
21
|
+
// Виден с md (1350px) и шире, ниже скрыт.
|
|
15
22
|
@include to-br(md) { ._from-md { display: none !important; } }
|
|
16
23
|
|
|
24
|
+
// Виден с lg (1792px) и шире, ниже скрыт.
|
|
17
25
|
@include to-br(lg) { ._from-lg { display: none !important; } }
|
|
18
26
|
|
|
27
|
+
// Обрезка текста в N строк: число строк задаётся --line-clamp (по умолчанию 1),
|
|
28
|
+
// выравнивание — --align, перенос — --word-break.
|
|
19
29
|
._truncate {
|
|
20
30
|
display: -webkit-box !important;
|
|
21
31
|
-webkit-line-clamp: var(--line-clamp, 1);
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
@use 'vars' as *;
|
|
2
2
|
@use '../mixins' as *;
|
|
3
3
|
|
|
4
|
+
// Корень страницы: колонка с вертикальными отступами. Вешается на корневой элемент в pages/**.
|
|
4
5
|
._page {
|
|
5
6
|
@include page;
|
|
6
7
|
}
|
|
7
8
|
|
|
9
|
+
// Хлебные крошки. Свой верхний отступ нужен, когда крошки лежат вне ._page.
|
|
8
10
|
._breadcrumbs {
|
|
9
11
|
margin-bottom: 24px;
|
|
10
12
|
padding-top: 24px;
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
// Крошки на страницах с инфо-блоком — отбивка снизу меньше.
|
|
13
16
|
._breadcrumbs-info {
|
|
14
17
|
margin-bottom: 16px;
|
|
15
18
|
padding-top: 24px;
|
|
@@ -20,20 +23,24 @@
|
|
|
20
23
|
padding-top: 0;
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
// Блок SEO-ссылок внизу страницы: прижат к низу и отбит линией.
|
|
23
27
|
._footer-links {
|
|
24
28
|
@include page-footer-links;
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
// Заголовок страницы: font-lg, uppercase, 700.
|
|
27
32
|
._title {
|
|
28
33
|
@include page-title;
|
|
29
34
|
|
|
30
35
|
margin-bottom: 24px;
|
|
31
36
|
}
|
|
32
37
|
|
|
38
|
+
// Строка фильтров/навигации под заголовком — только отбивка снизу.
|
|
33
39
|
._nav-filter {
|
|
34
40
|
margin-bottom: 32px;
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
// Центрирующий контейнер: боковые паддинги из --container-padding-inline, с md — max-width.
|
|
37
44
|
._container {
|
|
38
45
|
width: 100%;
|
|
39
46
|
margin: 0 auto;
|
|
@@ -44,6 +51,7 @@
|
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
// Пагинация внизу листинга — только отбивка сверху.
|
|
47
55
|
._pagination {
|
|
48
56
|
margin-top: 32px;
|
|
49
57
|
}
|
|
@@ -60,9 +60,9 @@ $brkpnts: (
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
Container query брейкпоинты
|
|
64
|
-
|
|
65
|
-
@include
|
|
63
|
+
Container query брейкпоинты — меряют контейнер, а не окно. Требуют `container-type`
|
|
64
|
+
на предке; значение только в единицах, ключей из $brkpnts тут нет.
|
|
65
|
+
@include from-cq(400px) { ... }
|
|
66
66
|
*/
|
|
67
67
|
@mixin from-cq($bp) {
|
|
68
68
|
@container (min-width: #{$bp}) {
|
|
@@ -70,6 +70,9 @@ $brkpnts: (
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
@include to-cq(400px) { ... }
|
|
75
|
+
*/
|
|
73
76
|
@mixin to-cq($bp) {
|
|
74
77
|
@container (max-width: #{$bp - 1px}) {
|
|
75
78
|
@content;
|
|
@@ -1,58 +1,70 @@
|
|
|
1
|
+
// 10px / 10px
|
|
1
2
|
@mixin font-4xs {
|
|
2
3
|
font-size: 10px;
|
|
3
4
|
line-height: 1;
|
|
4
5
|
}
|
|
5
6
|
|
|
7
|
+
// 11px / 11px
|
|
6
8
|
@mixin font-3xs {
|
|
7
9
|
font-size: 11px;
|
|
8
10
|
line-height: 1;
|
|
9
11
|
}
|
|
10
12
|
|
|
13
|
+
// 12px / 16px
|
|
11
14
|
@mixin font-xs {
|
|
12
15
|
font-size: 12px;
|
|
13
16
|
line-height: 1.333;
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
// 13px / 19.5px
|
|
16
20
|
@mixin font-2xs {
|
|
17
21
|
font-size: 13px;
|
|
18
22
|
line-height: 1.5;
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
// 14px / 20px
|
|
21
26
|
@mixin font-sm {
|
|
22
27
|
font-size: 14px;
|
|
23
28
|
line-height: 1.429;
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
// 15px / 22.5px
|
|
26
32
|
@mixin font-md {
|
|
27
33
|
font-size: 15px;
|
|
28
34
|
line-height: 1.5;
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
// 16px / 24px
|
|
31
38
|
@mixin font-base {
|
|
32
39
|
font-size: 16px;
|
|
33
40
|
line-height: 1.5;
|
|
34
41
|
}
|
|
35
42
|
|
|
43
|
+
// 18px / 28px
|
|
36
44
|
@mixin font-lg {
|
|
37
45
|
font-size: 18px;
|
|
38
46
|
line-height: 1.556;
|
|
39
47
|
}
|
|
40
48
|
|
|
49
|
+
// 20px / 28px
|
|
41
50
|
@mixin font-xl {
|
|
42
51
|
font-size: 20px;
|
|
43
52
|
line-height: 1.4;
|
|
44
53
|
}
|
|
45
54
|
|
|
55
|
+
// 24px / 32px
|
|
46
56
|
@mixin font-2xl {
|
|
47
57
|
font-size: 24px;
|
|
48
58
|
line-height: 1.333;
|
|
49
59
|
}
|
|
50
60
|
|
|
61
|
+
// 30px / 36px
|
|
51
62
|
@mixin font-3xl {
|
|
52
63
|
font-size: 30px;
|
|
53
64
|
line-height: 1.2;
|
|
54
65
|
}
|
|
55
66
|
|
|
67
|
+
// 36px / 40px
|
|
56
68
|
@mixin font-4xl {
|
|
57
69
|
font-size: 36px;
|
|
58
70
|
line-height: 1.111;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
@use 'font-sizes' as *;
|
|
3
3
|
@use 'vars' as *;
|
|
4
4
|
|
|
5
|
+
// Каркас страницы: колонка с вертикальными отступами. В разметке используется через
|
|
6
|
+
// глобальный класс `._page` (layout/_page.scss), напрямую в компонентах не нужен.
|
|
5
7
|
@mixin page {
|
|
6
8
|
display: flex;
|
|
7
9
|
flex-direction: column;
|
|
@@ -9,6 +11,7 @@
|
|
|
9
11
|
padding-bottom: 24px;
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
// Заголовок страницы: font-lg, uppercase, 700. В разметке — глобальный класс `._title`.
|
|
12
15
|
@mixin page-title {
|
|
13
16
|
@include font-lg;
|
|
14
17
|
|
|
@@ -17,6 +20,8 @@
|
|
|
17
20
|
font-weight: 700;
|
|
18
21
|
}
|
|
19
22
|
|
|
23
|
+
// Блок SEO-ссылок внизу страницы: прижат к низу (margin-top: auto) и отбит линией.
|
|
24
|
+
// В разметке — глобальный класс `._footer-links`.
|
|
20
25
|
@mixin page-footer-links {
|
|
21
26
|
margin-top: auto;
|
|
22
27
|
padding-top: 8px;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
@use 'font-sizes' as *;
|
|
3
3
|
@use 'vars' as *;
|
|
4
4
|
|
|
5
|
+
// Каркас карточки видео: колонка «постер + мета» с зазором 16px, контент прижат к верху.
|
|
5
6
|
@mixin video-card {
|
|
6
7
|
display: grid;
|
|
7
8
|
row-gap: 16px;
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
// Постер карточки: 16/9, скруглённый, с обрезкой — база для абсолютных бейджей поверх.
|
|
35
37
|
@mixin video-card-poster {
|
|
36
38
|
position: relative;
|
|
37
39
|
aspect-ratio: 16/9;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "itube-specs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.923",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"types": "./types/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -74,7 +74,8 @@
|
|
|
74
74
|
"assets/scss/base/",
|
|
75
75
|
"assets/scss/layout/",
|
|
76
76
|
"assets/scss/main.scss",
|
|
77
|
-
"nuxt.config.ts"
|
|
77
|
+
"nuxt.config.ts",
|
|
78
|
+
"DOCS/"
|
|
78
79
|
],
|
|
79
80
|
"devDependencies": {
|
|
80
81
|
"@intlify/eslint-plugin-vue-i18n": "^4.5.1",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Гарантирует `X-Domain` у запросов к BFF.
|
|
3
|
+
*
|
|
4
|
+
* Бекенд разруливает сайты по этому заголовку — у stripcash-ручек к домену
|
|
5
|
+
* привязан ещё и ключ провайдера, поэтому без него они отвечают 500
|
|
6
|
+
* («mux: route doesn't have a host»). Заголовок ставит `ApiHelper`, но не каждый
|
|
7
|
+
* вызов идёт через него: блок вебкамов, например, дёргает свою ручку обычным
|
|
8
|
+
* `useFetch`, и тогда до бекенда доезжали только проксированные заголовки запроса
|
|
9
|
+
* страницы — на локальной машине это прощалось, а за прокси на стенде ломалось.
|
|
10
|
+
*
|
|
11
|
+
* Значение берём из runtimeConfig сайта и НЕ перебиваем то, что уже пришло.
|
|
12
|
+
*/
|
|
13
|
+
export default defineEventHandler((event) => {
|
|
14
|
+
if (!event.path.startsWith('/api/') && !event.path.startsWith('/bff/')) return;
|
|
15
|
+
if (event.node.req.headers['x-domain']) return;
|
|
16
|
+
|
|
17
|
+
const xDomain = useRuntimeConfig(event).public.xDomain;
|
|
18
|
+
if (xDomain) event.node.req.headers['x-domain'] = String(xDomain);
|
|
19
|
+
});
|