@ai.all/ds7 1.0.0 → 2.0.0
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/01-architecture/ATOMIC-DESIGN.md +48 -42
- package/01-architecture/COMPONENT-ANATOMY.md +14 -30
- package/01-architecture/MODIFIER-SYSTEM.md +24 -35
- package/02-design-system/BRANDBOOK-SANDBOX.md +34 -34
- package/02-design-system/NAMING-CONVENTIONS.md +21 -19
- package/02-design-system/TOKENS-SPEC.md +65 -51
- package/03-performance-web-vitals/ASSETS-PIPELINE.md +40 -49
- package/03-performance-web-vitals/CRITICAL-CSS.md +20 -27
- package/03-performance-web-vitals/FONTS-ZERO-CLS.md +30 -34
- package/03-performance-web-vitals/HYDRATION-STRATEGY.md +38 -21
- package/04-seo-ai-optimization/AEO-GEO-AI-SEARCH.md +28 -45
- package/04-seo-ai-optimization/GOOGLE-LOCAL-EEAT.md +20 -25
- package/04-seo-ai-optimization/STRUCTURED-DATA.md +42 -37
- package/05-internationalization/I18N-ARCHITECTURE.md +27 -25
- package/05-internationalization/LOCALE-TEMPLATE.json +23 -22
- package/06-business-conversion/B2C-B2B-MODELS.md +22 -21
- package/06-business-conversion/CONTEXTUAL-LEADS.md +17 -22
- package/06-business-conversion/EV-SOLAR-ECOSYSTEM.md +21 -21
- package/07-testing/TESTING-STRATEGY.md +45 -83
- package/08-token-optimization/TOKEN-STRATEGY.md +54 -103
- package/ALL.md +203 -52
- package/CHANGELOG.md +27 -0
- package/README.md +92 -97
- package/SPEC.md +81 -37
- package/bin/ds7.js +183 -100
- package/package.json +10 -11
- package/templates/component.vue +10 -16
- package/templates/nuxt.config.ts +19 -25
- package/templates/theme.css +17 -10
- package/ANDALUSIA.md +0 -89
- package/ff.md +0 -112
|
@@ -1,88 +1,102 @@
|
|
|
1
|
-
# 02
|
|
1
|
+
# 02 · Design tokens
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Tokens are the foundations: the named values every component uses. They live as CSS custom properties in `app/assets/css/theme.css`. Components read tokens only; they never contain raw values.
|
|
4
|
+
|
|
5
|
+
Format for tools that need it: the [W3C Design Tokens Format Module (2025.10)](https://www.w3.org/community/reports/design-tokens/CG-FINAL-format-20251028/), with `$value` and `$type`.
|
|
4
6
|
|
|
5
7
|
---
|
|
6
8
|
|
|
7
|
-
## 1.
|
|
9
|
+
## 1. Colour
|
|
8
10
|
|
|
9
11
|
```css
|
|
10
12
|
:root {
|
|
11
|
-
/*
|
|
12
|
-
--color-paper: #f7f5ef;
|
|
13
|
-
--color-white: #ffffff;
|
|
14
|
-
--color-ink: #1f1b16;
|
|
15
|
-
--color-ink-500: #6b6459;
|
|
16
|
-
--color-ink-100: rgba(31, 27, 22, 0.08); /*
|
|
17
|
-
--color-ink-950: #12100d;
|
|
18
|
-
|
|
19
|
-
/*
|
|
20
|
-
--color-sun: #ffd84d;
|
|
21
|
-
--color-sun-hover: #ffcf2e;
|
|
22
|
-
|
|
23
|
-
/*
|
|
24
|
-
--color-success: #2e7d32;
|
|
25
|
-
--color-warning: #ed6c02;
|
|
26
|
-
--color-error: #d32f2f;
|
|
13
|
+
/* surfaces and text */
|
|
14
|
+
--color-paper: #f7f5ef; /* page background */
|
|
15
|
+
--color-white: #ffffff; /* cards, panels */
|
|
16
|
+
--color-ink: #1f1b16; /* main text */
|
|
17
|
+
--color-ink-500: #6b6459; /* secondary text */
|
|
18
|
+
--color-ink-100: rgba(31, 27, 22, 0.08); /* borders, dividers */
|
|
19
|
+
--color-ink-950: #12100d; /* dark inversions */
|
|
20
|
+
|
|
21
|
+
/* brand accent */
|
|
22
|
+
--color-sun: #ffd84d;
|
|
23
|
+
--color-sun-hover: #ffcf2e;
|
|
24
|
+
|
|
25
|
+
/* semantic */
|
|
26
|
+
--color-success: #2e7d32;
|
|
27
|
+
--color-warning: #ed6c02;
|
|
28
|
+
--color-error: #d32f2f;
|
|
27
29
|
}
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
**Contrast rule.** `--color-sun` is never used for text. It is a fill (buttons), a badge background, or a marker (`box-shadow: inset 0 -0.3em 0 var(--color-sun)`).
|
|
33
|
+
|
|
34
|
+
**Measure, do not estimate.** When a palette changes, check the contrast of every text and border pair with a tool, and record the result in the brandbook.
|
|
31
35
|
|
|
32
36
|
---
|
|
33
37
|
|
|
34
|
-
## 2.
|
|
38
|
+
## 2. Spacing
|
|
35
39
|
|
|
36
40
|
```css
|
|
37
41
|
:root {
|
|
38
|
-
--space-1: 0.25rem;
|
|
39
|
-
--space-2: 0.5rem;
|
|
40
|
-
--space-3: 0.75rem;
|
|
41
|
-
--space-4: 1rem;
|
|
42
|
-
--space-6: 1.5rem;
|
|
43
|
-
--space-8: 2rem;
|
|
44
|
-
--space-12: 3rem;
|
|
45
|
-
--space-16: 4rem;
|
|
46
|
-
|
|
47
|
-
/*
|
|
48
|
-
--space-single: clamp(1.2rem, 3vw, 2.2rem);
|
|
49
|
-
--space-double: clamp(2.4rem, 6vw, 4.5rem);
|
|
50
|
-
--space-triple: clamp(3.6rem, 9vw, 6.5rem);
|
|
51
|
-
|
|
52
|
-
/*
|
|
53
|
-
--content-width: 78rem;
|
|
42
|
+
--space-1: 0.25rem; /* 4 px */
|
|
43
|
+
--space-2: 0.5rem; /* 8 px */
|
|
44
|
+
--space-3: 0.75rem; /* 12 px */
|
|
45
|
+
--space-4: 1rem; /* 16 px */
|
|
46
|
+
--space-6: 1.5rem; /* 24 px */
|
|
47
|
+
--space-8: 2rem; /* 32 px */
|
|
48
|
+
--space-12: 3rem; /* 48 px */
|
|
49
|
+
--space-16: 4rem; /* 64 px */
|
|
50
|
+
|
|
51
|
+
/* responsive section rhythm */
|
|
52
|
+
--space-single: clamp(1.2rem, 3vw, 2.2rem);
|
|
53
|
+
--space-double: clamp(2.4rem, 6vw, 4.5rem);
|
|
54
|
+
--space-triple: clamp(3.6rem, 9vw, 6.5rem);
|
|
55
|
+
|
|
56
|
+
/* page grid */
|
|
57
|
+
--content-width: 78rem; /* 1248 px */
|
|
54
58
|
--page-gutter: clamp(1rem, 3.5vw, 2rem);
|
|
55
59
|
}
|
|
56
60
|
```
|
|
57
61
|
|
|
58
62
|
---
|
|
59
63
|
|
|
60
|
-
## 3.
|
|
61
|
-
|
|
62
|
-
Вместо случайных значений `box-shadow` используются строгие семантические токены:
|
|
64
|
+
## 3. Elevation
|
|
63
65
|
|
|
64
66
|
```css
|
|
65
67
|
:root {
|
|
66
|
-
|
|
67
|
-
--shadow-
|
|
68
|
+
--shadow-lift: 0 4px 16px -2px rgba(31, 27, 22, 0.06); /* resting cards */
|
|
69
|
+
--shadow-hover: 0 12px 32px -4px rgba(31, 27, 22, 0.12); /* hover, open items */
|
|
70
|
+
--shadow-modal: 0 24px 64px -8px rgba(18, 16, 13, 0.24); /* modal dialogs */
|
|
71
|
+
}
|
|
72
|
+
```
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
--shadow-hover: 0 12px 32px -4px rgba(31, 27, 22, 0.12);
|
|
74
|
+
Use these three. Do not write ad-hoc `box-shadow` values in components.
|
|
71
75
|
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 4. Radius
|
|
79
|
+
|
|
80
|
+
```css
|
|
81
|
+
:root {
|
|
82
|
+
--radius-sm: 0.375rem; /* 6 px — badges, chips */
|
|
83
|
+
--radius-md: 0.75rem; /* 12 px — cards, inputs */
|
|
84
|
+
--radius-lg: 1.25rem; /* 20 px — large section panels */
|
|
85
|
+
--radius-full: 9999px; /* pills, round buttons */
|
|
74
86
|
}
|
|
75
87
|
```
|
|
76
88
|
|
|
77
89
|
---
|
|
78
90
|
|
|
79
|
-
##
|
|
91
|
+
## 5. Breakpoints
|
|
80
92
|
|
|
81
93
|
```css
|
|
82
94
|
:root {
|
|
83
|
-
--
|
|
84
|
-
--
|
|
85
|
-
--
|
|
86
|
-
--
|
|
95
|
+
--bp-sm: 480px; /* one column below this */
|
|
96
|
+
--bp-md: 800px; /* tablet-portrait boundary */
|
|
97
|
+
--bp-lg: 900px; /* two-column grids collapse here */
|
|
98
|
+
--bp-xl: 1200px; /* desktop layout */
|
|
87
99
|
}
|
|
88
100
|
```
|
|
101
|
+
|
|
102
|
+
Media queries use these values. Components prefer container queries.
|
|
@@ -1,71 +1,62 @@
|
|
|
1
|
-
# 03
|
|
1
|
+
# 03 · Assets pipeline: tight trim and responsive images
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Images are prepared before they reach the code. Raw exports make pages heavy and products small.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 1.
|
|
7
|
+
## 1. Tight trim
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Typical problem: a product PNG exported on a 1000 × 1000 canvas where most pixels are empty transparent margin.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
* Файл весит лишние байты из-за пустых пикселей;
|
|
13
|
-
* Картинку невозможно ровно отцентрировать по сетке;
|
|
14
|
-
* На мобильных экранах сам товар становится крошечным из-за огромных невидимых полей.
|
|
11
|
+
Costs: extra bytes, off-centre objects in grids, and tiny products on phones.
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
1.
|
|
18
|
-
2.
|
|
19
|
-
3.
|
|
13
|
+
Rules:
|
|
14
|
+
1. **Crop to the object** along its outline, keeping a real soft shadow if the design has one.
|
|
15
|
+
2. **Keep transparency** for product cut-outs so they sit on light, dark and coloured backgrounds.
|
|
16
|
+
3. **Fix the box.** The container has a stable `aspect-ratio` (`4/3` or `1/1`); the image is centred with `object-fit: contain`.
|
|
20
17
|
|
|
21
18
|
---
|
|
22
19
|
|
|
23
|
-
## 2.
|
|
20
|
+
## 2. Responsive images
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
Every raster image ships in several sizes. The reference component is `UiImg`: it takes a `src` string, builds a `srcset` from the generated variants, and writes `width`, `height` and lazy-loading attributes.
|
|
23
|
+
|
|
24
|
+
Generated sizes (default): **400, 800 and 1200 px wide**, WebP, quality 82. Images smaller than 900 px are not resized up.
|
|
26
25
|
|
|
27
26
|
```html
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<source
|
|
37
|
-
srcset="/images/p/hero.webp 1x, /images/p/hero@2x.webp 2x"
|
|
38
|
-
type="image/webp"
|
|
39
|
-
>
|
|
40
|
-
<!-- 3. Фоллбэк img с явными габаритами для нулевого CLS -->
|
|
41
|
-
<img
|
|
42
|
-
src="/images/p/hero.webp"
|
|
43
|
-
alt="Instalación solar fotovoltaica en Sevilla"
|
|
44
|
-
width="1600"
|
|
45
|
-
height="900"
|
|
46
|
-
fetchpriority="high"
|
|
47
|
-
loading="eager"
|
|
48
|
-
decoding="async"
|
|
49
|
-
>
|
|
50
|
-
</picture>
|
|
27
|
+
<img
|
|
28
|
+
src="/images/p/hero-1200.webp"
|
|
29
|
+
srcset="/images/p/hero-400.webp 400w, /images/p/hero-800.webp 800w, /images/p/hero-1200.webp 1200w"
|
|
30
|
+
sizes="(max-width: 800px) 100vw, 60vw"
|
|
31
|
+
width="1200" height="900"
|
|
32
|
+
alt="Solar installation on a rooftop"
|
|
33
|
+
fetchpriority="high" loading="eager" decoding="async"
|
|
34
|
+
>
|
|
51
35
|
```
|
|
52
36
|
|
|
37
|
+
Use `UiImg` rather than writing this by hand in every page.
|
|
38
|
+
|
|
53
39
|
---
|
|
54
40
|
|
|
55
|
-
## 3.
|
|
41
|
+
## 3. Loading priorities
|
|
56
42
|
|
|
57
|
-
|
|
|
58
|
-
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
43
|
+
| Position | `loading` | `fetchpriority` | `decoding` |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| The LCP image above the fold | `eager` | `high` | `async` |
|
|
46
|
+
| Cards below the fold | `lazy` | `auto` | `async` |
|
|
47
|
+
| Footer and late sections | `lazy` | `low` | `async` |
|
|
48
|
+
|
|
49
|
+
Only one image is the LCP image on a page. Mark it, do not mark several.
|
|
62
50
|
|
|
63
51
|
---
|
|
64
52
|
|
|
65
|
-
## 4.
|
|
53
|
+
## 4. Asset script
|
|
54
|
+
|
|
55
|
+
Keep a small script in `tools/` (Sharp or Python) that:
|
|
56
|
+
- takes new images from a source folder;
|
|
57
|
+
- trims transparent margins;
|
|
58
|
+
- writes WebP (and AVIF where useful) at 400, 800 and 1200 px;
|
|
59
|
+
- writes `app/data/image-variants.json`, the manifest `UiImg` reads;
|
|
60
|
+
- has a `--check` mode that fails when a referenced image has no manifest entry.
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
* Принимает новые картинки;
|
|
69
|
-
* Обрезает прозрачные поля (`trim`);
|
|
70
|
-
* Конвертирует в WebP и AVIF с качеством 82%;
|
|
71
|
-
* Генерирует мобильные копии (`-400.webp`) и ретина-копии (`@2x.webp`).
|
|
62
|
+
Commit the manifest with the images it describes.
|
|
@@ -1,46 +1,39 @@
|
|
|
1
|
-
# 03
|
|
1
|
+
# 03 · Critical CSS and render-blocking
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A stylesheet in `<head>` blocks rendering until it has downloaded and parsed. On a slow mobile network a 50–150 KB stylesheet delays the first paint noticeably.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 1.
|
|
7
|
+
## 1. Split
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
2. Браузер находит в `<head>` тег `<link rel="stylesheet" href="/_nuxt/entry.css">`;
|
|
12
|
-
3. **Рендеринг страницы полностью блокируется (белый экран)**, пока файл `entry.css` (часто 50–150 КБ) скачивается по мобильной сети и парсится;
|
|
13
|
-
4. Пользователь видит контент только через 1.5–3.0 секунды.
|
|
9
|
+
- **Above the fold:** header, page grid, typography and the first screen. Needed for the first paint.
|
|
10
|
+
- **Below the fold:** calculators, reviews, map, footer, modals. Needed later.
|
|
14
11
|
|
|
15
12
|
---
|
|
16
13
|
|
|
17
|
-
## 2.
|
|
18
|
-
|
|
19
|
-
Critical CSS разделяет стили сайта на две части:
|
|
20
|
-
1. **Above-the-fold (Критический CSS):** Стили шапки, сетки контейнера, типографики и фонового блока первого экрана (Hero).
|
|
21
|
-
2. **Below-the-fold (Вторичный CSS):** Стили калькулятора, отзывов, карты, футера, модальных окон.
|
|
22
|
-
|
|
23
|
-
### Настройка в `nuxt.config.ts`:
|
|
14
|
+
## 2. Nuxt configuration
|
|
24
15
|
|
|
25
16
|
```ts
|
|
26
17
|
export default defineNuxtConfig({
|
|
27
18
|
experimental: {
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
}
|
|
19
|
+
inlineSSRStyles: true, // inline the CSS each page uses during generation
|
|
20
|
+
},
|
|
31
21
|
})
|
|
32
22
|
```
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
During `nuxt generate`:
|
|
25
|
+
- each page is rendered and the classes it uses are collected;
|
|
26
|
+
- those rules are inlined in `<style>` in the page `<head>`;
|
|
27
|
+
- the full stylesheet is loaded afterwards with `rel="preload"`.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 3. What to measure
|
|
32
|
+
|
|
33
|
+
Record First Contentful Paint and render-blocking requests on a real phone, with the build and date, in `docs/METRICS.md`. Lab numbers from a sandbox are not evidence for this.
|
|
39
34
|
|
|
40
35
|
---
|
|
41
36
|
|
|
42
|
-
##
|
|
37
|
+
## 4. Budget
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
* **FCP (First Contentful Paint):** снижается с 1.8s до **0.35s**.
|
|
46
|
-
* **Speed Index:** зеленая зона (98–100 баллов).
|
|
39
|
+
Keep the entry stylesheet small enough to inline without delaying HTML. Set a budget in `docs/METRICS.md` and check it when the stylesheet changes.
|
|
@@ -1,43 +1,39 @@
|
|
|
1
|
-
# 03
|
|
1
|
+
# 03 · Fonts and zero layout shift
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Text that jumps when a web font arrives moves the whole page and raises CLS. This document describes how DS7 avoids that.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 1.
|
|
7
|
+
## 1. The problem
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
3. Через секунду подгружается `montserrat.woff2`, шрифт переключается, строки переносятся на новое место, и **вся страница дергается вниз на 20–40 пикселей**.
|
|
13
|
-
4. Пользователь случайно кликает не туда, а Google фиксирует плохой показатель CLS (> 0.15).
|
|
9
|
+
1. The browser draws text with a system font first (Arial or Helvetica).
|
|
10
|
+
2. The system font has different glyph widths and line heights from the brand font.
|
|
11
|
+
3. When the brand font arrives, lines re-wrap and content moves, often 20–40 px.
|
|
14
12
|
|
|
15
13
|
---
|
|
16
14
|
|
|
17
|
-
## 2.
|
|
15
|
+
## 2. The approach
|
|
18
16
|
|
|
19
|
-
###
|
|
20
|
-
|
|
17
|
+
### Step 1: preload one weight
|
|
18
|
+
Preload the weight used on the first screen, as `woff2`, from the document `<head>`:
|
|
21
19
|
|
|
22
20
|
```ts
|
|
23
|
-
// nuxt.config.ts
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
]
|
|
21
|
+
// nuxt.config.ts → app.head.link
|
|
22
|
+
{
|
|
23
|
+
rel: 'preload',
|
|
24
|
+
href: '/fonts/brand-latin-var.woff2',
|
|
25
|
+
as: 'font',
|
|
26
|
+
type: 'font/woff2',
|
|
27
|
+
crossorigin: 'anonymous',
|
|
28
|
+
}
|
|
33
29
|
```
|
|
34
30
|
|
|
35
|
-
###
|
|
36
|
-
|
|
31
|
+
### Step 2: matched fallback
|
|
32
|
+
A fallback `@font-face` built from a system font, with metrics adjusted to the brand font:
|
|
37
33
|
|
|
38
34
|
```css
|
|
39
35
|
@font-face {
|
|
40
|
-
font-family: '
|
|
36
|
+
font-family: 'Brand Fallback';
|
|
41
37
|
src: local('Arial');
|
|
42
38
|
ascent-override: 95.8%;
|
|
43
39
|
descent-override: 28.2%;
|
|
@@ -46,18 +42,18 @@ Fontaine анализирует геометрию шрифта Montserrat и с
|
|
|
46
42
|
}
|
|
47
43
|
```
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
Когда реальный шрифт скачивается, форма букв меняется, но **ни одна строчка на странице не сдвигается**.
|
|
45
|
+
The values come from the brand font's metrics (the `@nuxtjs/fontaine` module computes them). When the brand font loads, glyphs have the same advance widths and line boxes, so text does not move.
|
|
51
46
|
|
|
52
|
-
###
|
|
53
|
-
|
|
47
|
+
### Step 3: `font-display: swap`
|
|
48
|
+
Text appears at once with the fallback and swaps when the brand font is ready. No invisible text.
|
|
54
49
|
|
|
55
50
|
---
|
|
56
51
|
|
|
57
|
-
## 3.
|
|
52
|
+
## 3. Checklist
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
- [ ] One preloaded weight for the first screen.
|
|
55
|
+
- [ ] Fallback with `size-adjust` and the override metrics.
|
|
56
|
+
- [ ] Every logo and inline SVG has `width` and `height`.
|
|
57
|
+
- [ ] Every card and media box has a fixed `aspect-ratio`.
|
|
58
|
+
- [ ] Fixed-position panels (cookie banner, intro) do not push content.
|
|
59
|
+
- [ ] Measured CLS recorded in `docs/METRICS.md` on a real device.
|
|
@@ -1,51 +1,68 @@
|
|
|
1
|
-
# 03
|
|
1
|
+
# 03 · Hydration strategy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Static HTML appears first. Then the framework attaches event handlers to each component (hydration). On a phone with many interactive components this can block the main thread for hundreds of milliseconds, and taps are ignored during that time.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 1.
|
|
7
|
+
## 1. Principle
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
* Vue начинает «оживлять» каждый компонент на странице, вешать слушатели событий и инициализировать реактивные объекты;
|
|
11
|
-
* Если на странице 15 секций (калькулятор, карта проектов, чат, модалки, слайдеры), этот процесс может занять **250–500 мс** на среднем мобильном процессоре;
|
|
12
|
-
* В это время страница «висит» — пользователь тыкает по меню или кнопке, а клик не срабатывает.
|
|
9
|
+
Hydrate only what the visitor needs now.
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
- **Critical:** navigation, the first interactive element, forms the visitor sees.
|
|
12
|
+
- **Idle:** chat widgets, cookie banner, sound controls, modals that are not open.
|
|
13
|
+
- **On visibility:** heavy sections below the fold (maps, charts, interactive calculators).
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
---
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
Виджеты, которые не нужны в первую секунду (чат WhatsApp, куки-баннер, модалка звонка, звуковые эффекты), гидрируются только тогда, когда процессор свободен:
|
|
17
|
+
## 2. Idle widgets
|
|
20
18
|
|
|
21
19
|
```vue
|
|
22
20
|
<script setup lang="ts">
|
|
23
|
-
const
|
|
21
|
+
const ready = ref(false)
|
|
24
22
|
|
|
25
23
|
onMounted(() => {
|
|
26
24
|
if ('requestIdleCallback' in window) {
|
|
27
|
-
window.requestIdleCallback(() => {
|
|
25
|
+
window.requestIdleCallback(() => { ready.value = true })
|
|
28
26
|
} else {
|
|
29
|
-
setTimeout(() => {
|
|
27
|
+
setTimeout(() => { ready.value = true }, 1500)
|
|
30
28
|
}
|
|
31
29
|
})
|
|
32
30
|
</script>
|
|
33
31
|
|
|
34
32
|
<template>
|
|
35
33
|
<ClientOnly>
|
|
36
|
-
<SiteChat v-if="
|
|
37
|
-
<SiteCookieBanner v-if="
|
|
34
|
+
<SiteChat v-if="ready" />
|
|
35
|
+
<SiteCookieBanner v-if="ready" />
|
|
38
36
|
</ClientOnly>
|
|
37
|
+
}
|
|
39
38
|
</template>
|
|
40
39
|
```
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
Note for the banner: it must not hide content while it waits. Reserve its space with a fixed position.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. Visibility
|
|
46
|
+
|
|
47
|
+
Heavy sections use lazy components that hydrate when they approach the viewport:
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<LazyHomeProjectMap hydrate-on-visible />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 4. Passive listeners
|
|
56
|
+
|
|
57
|
+
Scroll and touch listeners that never call `preventDefault` are registered as passive, so the browser can scroll without waiting for the script:
|
|
44
58
|
|
|
45
|
-
### 3. Пассивные слушатели событий (`passive: true`)
|
|
46
|
-
В компоненте `UiCardSlider` и любых событиях скролла/тача:
|
|
47
59
|
```ts
|
|
48
60
|
window.addEventListener('scroll', onScroll, { passive: true })
|
|
49
61
|
track.addEventListener('touchstart', onTouchStart, { passive: true })
|
|
50
62
|
```
|
|
51
|
-
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 5. Measuring
|
|
67
|
+
|
|
68
|
+
Total Blocking Time and Interaction to Next Paint are measured on a real device, not in a sandbox. Record the results in `docs/METRICS.md`.
|