design-systems: add 74 brand DESIGN.md files (VoltAgent/awesome-design-md)
- 74 brands: airbnb, stripe, apple, figma, linear, vercel, etc. - 147 files: DESIGN.md + README.md per brand (~2.1 MB) - Each DESIGN.md contains structured brand analysis (colors, typography, components) - Updated 00-overview.md with new design-systems directory
This commit is contained in:
1 parent
e3738f2b3e
commit
76c4c5b4fe
233 files changed
+54886
-1
No files matched your search
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: media/animated-illustration
|
||||
title: Animated Illustrations & Icons
|
||||
description: Lottie animations, CSS illustrations, motion graphics.
|
||||
tags: [media, animation, illustration, lottie, motion]
|
||||
source: raycast.com, linear.app, amppz.com, brad.co
|
||||
---
|
||||
|
||||
# Animated Illustrations
|
||||
|
||||
## 1. Lottie Integration
|
||||
|
||||
```html
|
||||
<div id="animation" data-src="animation.json"></div>
|
||||
<script src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js"></script>
|
||||
<script>
|
||||
document.getElementById('animation').innerHTML = `
|
||||
<lottie-player src="animation.json" background="transparent" speed="1" style="width: 300px; height: 300px;" loop autoplay></lottie-player>
|
||||
`;
|
||||
</script>
|
||||
```
|
||||
|
||||
## 2. CSS Animated Illustration
|
||||
|
||||
```css
|
||||
.illustration {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: conic-gradient(from 0deg, #6366f1, #ec4899, #6366f1);
|
||||
border-radius: 50%;
|
||||
animation: spin 4s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| ❌ Плохо | ✅ Хорошо |
|
||||
|----------|-----------|
|
||||
| Без fallback | SVG fallback |
|
||||
| Без `prefers-reduced-motion` | Уважать настройки |
|
||||
@@ -0,0 +1,222 @@
|
||||
# Blender Pipeline — Figma → PatternGen → 3D Compositing
|
||||
|
||||
> Полный конвейер от Figma-дизайна до 3D-compositing в Blender.
|
||||
> Основано на [patterngen-oss](https://github.com/halfof8/patterngen-oss) workflow.
|
||||
|
||||
## Обзор
|
||||
|
||||
```
|
||||
Figma (artboard 1920×1080)
|
||||
↓ Plugin "PatternGen Scene Export"
|
||||
Scene JSON (titles as 2× PNG data URLs + colors + seed)
|
||||
↓ Load в PatternGen web app
|
||||
Procedural pattern generation (Canvas 2D)
|
||||
↓ EXPORT (JSZip + FileSaver)
|
||||
ZIP: scene_titles/*.png, scene_patterns/*.png, scene_dots/*.png
|
||||
↓ Blender addon "PatternGen Importer"
|
||||
3 emissive planes с alpha-blending → Cycles/Eevee render
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Этап 1: Figma → Scene JSON
|
||||
|
||||
### Подготовка артборда
|
||||
|
||||
- Frame размер: **1920×1080 px** (Full HD)
|
||||
- Grid: **20 px** (Layout Grid → Grid → 20 px)
|
||||
- Каждый title — top-level child frame, размеры кратны 20
|
||||
- Название фрейма = `sceneName` (префикс для экспорта)
|
||||
- Цвета: любые (плагин сканирует fills/strokes)
|
||||
|
||||
### Экспорт
|
||||
|
||||
1. Выбрать один или несколько фреймов 1920×1080
|
||||
2. **Plugins → Development → PatternGen Scene Export**
|
||||
3. **Export Selected Frames**
|
||||
4. Результат: `<frameName>_patterngen.json`
|
||||
|
||||
### Структура JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"sceneName": "My Title",
|
||||
"seed": 123456789, // 31-bit integer, determinism
|
||||
"colors": ["#FF0000", "#00FF00", "#0000FF"], // detected colors
|
||||
"titles": [
|
||||
{
|
||||
"id": "title_1",
|
||||
"x": 100, // snapped to 20px grid
|
||||
"y": 200,
|
||||
"w": 400,
|
||||
"h": 100,
|
||||
"img": "data:image/png;base64,..." // 2× retina PNG
|
||||
}
|
||||
],
|
||||
"defaults": {
|
||||
"durationMs": 6000,
|
||||
"density": 10,
|
||||
"proximity": 2,
|
||||
"stagger": 3,
|
||||
"theme": "dark"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Что экспортируется
|
||||
|
||||
- **Titles**: 2× PNG (retina), data URL, позиции snapped к сетке
|
||||
- **Color set**: уникальные цвета fills/strokes (кроме #000 и #FFF)
|
||||
- **Seed**: 31-bit integer — детерминированная генерация паттернов
|
||||
- **Scene defaults**: продолжительность, плотность, proximity, stagger, тема
|
||||
|
||||
---
|
||||
|
||||
## Этап 2: PatternGen Web App
|
||||
|
||||
### Загрузка
|
||||
|
||||
1. Открыть PatternGen (localhost:5173)
|
||||
2. Кнопка **LOAD** → выбрать `*_patterngen.json`
|
||||
3. Паттерны генерируются **детерминированно по seed**
|
||||
4. Сцена сразу готова к **PLAY**
|
||||
|
||||
### Ручное редактирование
|
||||
|
||||
- **Drag & Drop** PNG/JPG изображений на canvas (snap to 20 px grid)
|
||||
- **Drag** для перемещения, drag off canvas для удаления
|
||||
- **+ ADD COLOR** — до 3 кастомных цветов (color picker)
|
||||
- Клик по swatch — toggle on/off
|
||||
|
||||
### Параметры
|
||||
|
||||
| Параметр | Диапазон | Описание |
|
||||
|-------------|---------|----------|
|
||||
| **Density** | 1–20 | Количество паттерн-элементов |
|
||||
| **Proximity** | 1–20 | Минимальное расстояние от titles |
|
||||
| **Stagger** | 0–5 | Разброс animation delay |
|
||||
| **Grid** | toggle | 20 px reference grid |
|
||||
| **Theme** | dark/light | UI theme |
|
||||
|
||||
---
|
||||
|
||||
## Этап 3: Экспорт PNG-секвенций
|
||||
|
||||
### Формат ZIP
|
||||
|
||||
```
|
||||
scene_pattern_gen.zip
|
||||
├── titles/
|
||||
│ scene_titles_00000.png
|
||||
│ scene_titles_000001.png
|
||||
│ ...
|
||||
├── patterns/
|
||||
│ scene_patterns_00000.png
|
||||
│ ...
|
||||
└── dots/
|
||||
scene_dots_00000.png
|
||||
...
|
||||
```
|
||||
|
||||
### Параметры экспорта
|
||||
|
||||
- **Scale**: 4× (EXPORT_SCALE = 4 → 7680×4320px)
|
||||
- **FPS**: 24
|
||||
- **Фон**: прозрачный (RGBA)
|
||||
- **Слои**: 3 отдельных (titles, patterns, dots)
|
||||
|
||||
### Фазы рендера
|
||||
|
||||
```
|
||||
titles: durationMs (1×) — plays once
|
||||
patterns: durationMs (1×) — plays once
|
||||
dots: 4000ms loop — infinite loop
|
||||
```
|
||||
|
||||
### Easing при экспорте
|
||||
|
||||
Выбирается один из 7 типов:
|
||||
`linear`, `easeIn`, `easeOut`, `easeInOut`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`
|
||||
|
||||
---
|
||||
|
||||
## Этап 4: Blender Import
|
||||
|
||||
### Установка
|
||||
|
||||
1. Blender → **Edit → Preferences → Add-ons → Install…**
|
||||
2. Выбрать `patterngen_importer.py`
|
||||
3. Поставить галочку для активации
|
||||
4. Открыть N-panel → вкладка **PatternGen**
|
||||
|
||||
### Импорт сцены
|
||||
|
||||
1. Установить **Scene Folder** на распакованную папку
|
||||
2. Кнопка **Import Scene Folders**
|
||||
3. Три planes создаются в origin
|
||||
|
||||
### Конфигурация слоёв
|
||||
|
||||
| Layer | Cyclic | Location (m) | Rotation | Scale |
|
||||
|-------|--------|-------------|----------|-------|
|
||||
| titles | off | (0, 0, 0) | 90° на X | 6×6×6 |
|
||||
| patterns | off | (0, 0.1, 0) | 90° на X | 6×6×6 |
|
||||
| dots | **on** | (0, 0.2, 0) | 90° на X | 6×6×6 |
|
||||
|
||||
### Node Tree материалов
|
||||
|
||||
```
|
||||
Image Texture (SEQUENCE, auto-refresh ON)
|
||||
Color ─┬─► Principled BSDF › Base Color
|
||||
└─► Principled BSDF › Emission Color (strength = 1)
|
||||
Alpha ───► Principled BSDF › Alpha
|
||||
```
|
||||
|
||||
### Image Sequence настройки
|
||||
|
||||
- **Start Frame**: 0
|
||||
- **Frames**: количество PNG в папке
|
||||
- **Offset**: 0
|
||||
- **Auto Refresh**: ON
|
||||
- **Cyclic**: ON для `dots`, OFF для `titles`/`patterns`
|
||||
|
||||
### Sync Start Frame
|
||||
|
||||
1. Выбрать plane(s) (Shift+click или A для всех)
|
||||
2. В секции **Sync Start Frame** ввести новое значение
|
||||
3. **Apply to Selected** — все image-sequence текстуры обновлены
|
||||
|
||||
### Замечания
|
||||
|
||||
- **Blend Method**: `BLEND` (alpha renders clean в Eevee/Cycles)
|
||||
- При артефактах сортировки переключить на `HASHED`
|
||||
- Повторный импорт создаёт дубликаты (`.001`, `.002`) — удалить вручную
|
||||
- Отступ 0.1m между слоями — только для предотвращения z-fighting
|
||||
- Plane scale настроен по aspect ratio изображения (16:9)
|
||||
|
||||
---
|
||||
|
||||
## Полный конвейер в цифрах
|
||||
|
||||
| Параметр | Значение |
|
||||
|----------|---------|
|
||||
| Canvas | 1920×1080 |
|
||||
| Export Scale | 4× (7680×4320) |
|
||||
| FPS | 24 |
|
||||
| Grid | 20 px |
|
||||
| Duration | 6000ms (default) |
|
||||
| Dots Loop | 4000ms |
|
||||
| Cycle | 4× duration = 24s |
|
||||
| Patterns | 7 animation types |
|
||||
| Colors | 2 fixed + 3 custom max |
|
||||
| Export Layers | 3 (titles, patterns, dots) |
|
||||
|
||||
---
|
||||
|
||||
## Референсы кода
|
||||
|
||||
- `figma-plugin/README.md` — Figma plugin setup, export format
|
||||
- `blender-addon/README.md` — Blender addon installation, node tree
|
||||
- `src/export/exporter.ts` — PNG sequence export, JSZip, FileSaver
|
||||
- `src/engine/timeline.ts` — Phase 1/2/3 timing, dots loop
|
||||
- `src/types.ts` — CANVAS_W/H, EXPORT_SCALE, FPS, GRID_SIZE
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: media/image-parallax
|
||||
title: Image Parallax
|
||||
description: Images moving at different scroll speeds. Creates depth.
|
||||
tags: [media, parallax, image, scroll, depth]
|
||||
source: apple.com, amppz.com, brad.co, godly.website
|
||||
---
|
||||
|
||||
# Image Parallax
|
||||
|
||||
## CSS-only реализация
|
||||
|
||||
```css
|
||||
.parallax-section {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.parallax-image {
|
||||
position: absolute;
|
||||
top: -20%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 140%;
|
||||
background: url('/parallax.jpg') center/cover;
|
||||
will-change: transform;
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.scrollY;
|
||||
const parallax = document.querySelector('.parallax-image');
|
||||
if (parallax) {
|
||||
parallax.style.transform = `translateY(${scrolled * 0.5}px)`;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## GSAP версия
|
||||
|
||||
```javascript
|
||||
gsap.to('.parallax-image', {
|
||||
yPercent: 50,
|
||||
ease: 'none',
|
||||
scrollTrigger: {
|
||||
trigger: '.parallax-section',
|
||||
start: 'top bottom',
|
||||
end: 'bottom top',
|
||||
scrub: true
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| ❌ Плохо | ✅ Хорошо |
|
||||
|----------|-----------|
|
||||
| Без `will-change` | `will-change: transform` |
|
||||
| `scrub: 0` | `scrub: 1` |
|
||||
| Без `prefers-reduced-motion` | Отключать на мобильных |
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
name: media/svg-animation
|
||||
title: SVG Animation & Icon Motion
|
||||
description: Animated SVG icons, paths, and illustrations.
|
||||
tags: [media, svg, animation, icon, motion]
|
||||
source: raycast.com, linear.app, brad.co, amppz.com
|
||||
---
|
||||
|
||||
# SVG Animation
|
||||
|
||||
## 1. Path Draw Animation
|
||||
|
||||
```html
|
||||
<svg class="icon-animated" viewBox="0 0 24 24" width="48" height="48">
|
||||
<path class="icon-path" d="M5 13l4 4L19 7" fill="none" stroke="#6366f1" stroke-width="2"/>
|
||||
</svg>
|
||||
```
|
||||
|
||||
```css
|
||||
.icon-path {
|
||||
stroke-dasharray: 30;
|
||||
stroke-dashoffset: 30;
|
||||
animation: draw 0.5s ease forwards;
|
||||
}
|
||||
@keyframes draw {
|
||||
to { stroke-dashoffset: 0; }
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Morphing SVG
|
||||
|
||||
```html
|
||||
<svg class="morph-svg" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="40" fill="#6366f1">
|
||||
<animate attributeName="r" values="40;50;40" dur="3s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</svg>
|
||||
```
|
||||
|
||||
## 3. Icon Hover
|
||||
|
||||
```css
|
||||
.icon {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.icon:hover {
|
||||
transform: scale(1.2) rotate(10deg);
|
||||
}
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| ❌ Плохо | ✅ Хорошо |
|
||||
|----------|-----------|
|
||||
| Без viewBox | Всегда viewBox |
|
||||
| Без `stroke-dashoffset` | Использовать для path |
|
||||
| 500ms+ анимация | 0.3–0.5s |
|
||||
@@ -0,0 +1,316 @@
|
||||
---
|
||||
name: media/video-mask
|
||||
title: Video Background with Mask & Clip
|
||||
description: Video displayed inside shapes, text, or masks. Creates striking visual impact.
|
||||
tags: [media, video, mask, clip, effect, wow]
|
||||
source: amppz.com, brad.co, godly.website, linear.app
|
||||
---
|
||||
|
||||
# Video Background with Mask & Clip
|
||||
|
||||
## Что это
|
||||
Видео, отображаемое не как прямоугольник, а внутри формы: маски, обрезка по тексту, SVG-маски. Создаёт премиальный wow-эффект.
|
||||
|
||||
## Типы масок для видео
|
||||
|
||||
### 1. Text Mask (видео внутри текста)
|
||||
```html
|
||||
<section class="hero-video-text">
|
||||
<h1 class="text-mask-video">
|
||||
PREVIEW
|
||||
</h1>
|
||||
</section>
|
||||
```
|
||||
|
||||
```css
|
||||
.text-mask-video {
|
||||
font-size: clamp(4rem, 12vw, 10rem);
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
background: url('/video-preview.mp4') center/cover;
|
||||
-webkit-mask-image: text;
|
||||
mask-image: text;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
mask-position: center;
|
||||
-webkit-mask-size: contain;
|
||||
mask-size: contain;
|
||||
color: #0a0a0a; /* fallback color */
|
||||
margin: 0 auto;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Circle Mask (видео в круге)
|
||||
```html
|
||||
<div class="video-circle">
|
||||
<video autoplay muted loop playsinline>
|
||||
<source src="/hero-video.webm" type="video/webm">
|
||||
</video>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.video-circle {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.video-circle video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
/* Увеличиваем чтобы заполнить круг */
|
||||
transform: scale(1.2);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. SVG Mask (произвольная форма)
|
||||
```html
|
||||
<svg style="position: absolute; width: 0; height: 0;">
|
||||
<defs>
|
||||
<mask id="star-mask">
|
||||
<rect width="100%" height="100%" fill="white"/>
|
||||
<path d="M150 0 L200 100 L300 100 L220 160 L250 260 L150 210 L50 260 L80 160 L0 100 L100 100 Z"
|
||||
fill="black"/>
|
||||
</mask>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div class="video-masked" style="mask: url(#star-mask); -webkit-mask: url(#star-mask);">
|
||||
<video autoplay muted loop playsinline>
|
||||
<source src="/hero-video.webm" type="video/webm">
|
||||
</video>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.video-masked {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.video-masked video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Clip-path Polygon
|
||||
```css
|
||||
.video-polygon {
|
||||
clip-path: polygon(20% 0%, 80% 0%, 100% 50%, 80% 100%, 20% 100%, 0% 50%);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.video-polygon video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Video as Hero Background
|
||||
```html
|
||||
<section class="hero-video-bg">
|
||||
<div class="hero-overlay"></div>
|
||||
<div class="hero-content">
|
||||
<h1>Заголовок поверх видео</h1>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
```css
|
||||
.hero-video-bg {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-video-bg video {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
transform: translate(-50%, -50%);
|
||||
object-fit: cover;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.hero-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 120px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
```
|
||||
|
||||
## Performance-Optimized Video Setup
|
||||
|
||||
### WebM + MP4 Fallback
|
||||
```html
|
||||
<video autoplay muted loop playsinline poster="/video-poster.jpg" preload="metadata">
|
||||
<source src="/video.webm" type="video/webm">
|
||||
<source src="/video.mp4" type="video/mp4">
|
||||
<!-- Fallback image -->
|
||||
<img src="/video-poster.jpg" alt="" class="video-fallback">
|
||||
</video>
|
||||
```
|
||||
|
||||
```css
|
||||
.video-fallback {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* Показываем fallback если видео не загрузилось */
|
||||
video:not(:success) + .video-fallback {
|
||||
display: block;
|
||||
}
|
||||
```
|
||||
|
||||
### Responsive Video Sizing
|
||||
```css
|
||||
.video-responsive {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.video-responsive {
|
||||
aspect-ratio: 9 / 16;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Lazy Loading Video
|
||||
|
||||
```html
|
||||
<!-- Видео загружается только при появлении в viewport -->
|
||||
<video class="video-lazy"
|
||||
data-src="/video.webm"
|
||||
data-src-mp4="/video.mp4"
|
||||
autoplay muted loop playsinline>
|
||||
</video>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// IntersectionObserver для ленивой загрузки
|
||||
const videoObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const video = entry.target;
|
||||
video.src = video.dataset.src;
|
||||
video.load();
|
||||
videoObserver.unobserve(video);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
rootMargin: '200px'
|
||||
});
|
||||
|
||||
document.querySelectorAll('.video-lazy').forEach(video => {
|
||||
videoObserver.observe(video);
|
||||
});
|
||||
```
|
||||
|
||||
## Video with Noise Overlay (premium feel)
|
||||
```html
|
||||
<div class="video-container">
|
||||
<video autoplay muted loop playsinline>
|
||||
<source src="/hero-video.webm" type="video/webm">
|
||||
</video>
|
||||
<div class="noise-overlay"></div>
|
||||
<div class="gradient-overlay"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.noise-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url('/noise.png');
|
||||
background-size: 256px 256px;
|
||||
opacity: 0.03;
|
||||
mix-blend-mode: overlay;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gradient-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
transparent 0%,
|
||||
rgba(10, 10, 10, 0.3) 50%,
|
||||
rgba(10, 10, 10, 0.9) 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| ❌ Плохо | ✅ Хорошо |
|
||||
|----------|-----------|
|
||||
| `.mp4` без `.webm` fallback | WebM + MP4 + poster fallback |
|
||||
| Без `preload="metadata"` | `preload="metadata"` + lazy load |
|
||||
| Видео > 10MB | Сжать до < 5MB (HandBrake) |
|
||||
| Без `playsinline` на мобильных | Всегда `playsinline` для iOS |
|
||||
| Без poster | Всегда `poster` как fallback |
|
||||
| Звук | Никогда не включать звук на автоплеер |
|
||||
|
||||
## Compression Pipeline
|
||||
|
||||
```bash
|
||||
# HandBrake CLI — сжатие видео для веба
|
||||
HandBrakeCLI -i input.mp4 -o output.webm \
|
||||
--format webm \
|
||||
-e libvpx-vp9 \
|
||||
-q 25 \
|
||||
--width 1920 \
|
||||
--max-height 1080 \
|
||||
--two-pass \
|
||||
--turbo 2
|
||||
|
||||
# FFmpeg — альтернатива
|
||||
ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 1500k \
|
||||
-c:a libopus -b:a 128k -f webm output.webm
|
||||
```
|
||||
|
||||
## CV Service Validation
|
||||
|
||||
```bash
|
||||
# Проверить performance (video loading impact)
|
||||
curl -s -X POST http://localhost:8001/design/analyze-dom \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"html": "...", "full_analysis": false}' | python3 -c "
|
||||
import sys, json
|
||||
d = json.load(sys.stdin)
|
||||
perf = d['analyzers'].get('performance', {})
|
||||
print(f\"Performance: {perf.get('score', 'N/A')}/10\")
|
||||
print(f\" LCP: {perf.get('lcp_impact', 'N/A')}\")
|
||||
print(f\" Largest Element: {perf.get('largest_element', 'N/A')}\")
|
||||
"
|
||||
```
|
||||
|
||||
Ожидается: `performance` > 7 (video должен быть оптимизирован).
|
||||
Reference in new issue
Block a user