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,121 @@
|
||||
---
|
||||
name: system/component-patterns
|
||||
title: Component Patterns
|
||||
description: Reusable UI components with consistent patterns.
|
||||
tags: [system, component, patterns, reusability]
|
||||
source: raycast.com, linear.app, stripe.com, design-systems.dev
|
||||
---
|
||||
|
||||
# Component Patterns
|
||||
|
||||
## 1. Button Variants
|
||||
|
||||
```css
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px 24px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: var(--color-accent-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
border-color: var(--color-accent);
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.btn-ghost:hover {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.btn-lg { padding: 16px 32px; font-size: 1.125rem; }
|
||||
.btn-sm { padding: 8px 16px; font-size: 0.875rem; }
|
||||
```
|
||||
|
||||
## 2. Input Variants
|
||||
|
||||
```css
|
||||
.input {
|
||||
padding: 12px 16px;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 10px;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Card Variants
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 20px;
|
||||
padding: 32px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.card:hover {
|
||||
border-color: var(--color-border-hover);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Badge/Dot
|
||||
|
||||
```css
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 12px;
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
color: var(--color-accent);
|
||||
border-radius: 20px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| ❌ Плохо | ✅ Хорошо |
|
||||
|----------|-----------|
|
||||
| 10+ размеров кнопок | 3–5 вариантов |
|
||||
| Без `:focus` | `:focus-visible` outline |
|
||||
| Без transition | `transition: all 0.3s ease` |
|
||||
| Hover на touch | `@media (hover: hover)` |
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: system/css-architecture
|
||||
title: CSS Architecture Best Practices
|
||||
description: Methodologies, file structure, naming conventions.
|
||||
tags: [system, css, architecture, bem, oocss]
|
||||
source: stripe.com, raycast.com, design-systems.dev, css-tricks.com
|
||||
---
|
||||
|
||||
# CSS Architecture
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
css/
|
||||
├── tokens/ # Design tokens
|
||||
├── base/ # Reset, typography
|
||||
├── components/ # Buttons, cards, inputs
|
||||
├── layouts/ # Grid, containers
|
||||
├── pages/ # Page-specific styles
|
||||
└── utilities/ # Helpers
|
||||
```
|
||||
|
||||
## BEM Naming
|
||||
|
||||
```css
|
||||
/* Block */
|
||||
.card { }
|
||||
|
||||
/* Element */
|
||||
.card__title { }
|
||||
.card__image { }
|
||||
|
||||
/* Modifier */
|
||||
.card--featured { }
|
||||
.card--dark { }
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| ❌ Плохо | ✅ Хорошо |
|
||||
|----------|-----------|
|
||||
| `!important` | BEM specificity |
|
||||
| Hardcoded colors | CSS variables |
|
||||
| 1000+ line file | Split into modules |
|
||||
@@ -0,0 +1,292 @@
|
||||
---
|
||||
name: system/design-tokens
|
||||
title: Design Tokens System
|
||||
description: Complete CSS custom properties system for consistent spacing, typography, color, and motion tokens.
|
||||
tags: [system, design-tokens, css-variables, architecture, consistency]
|
||||
source: stripe.com, linear.app, raycast.com, design-systems.dev
|
||||
---
|
||||
|
||||
# Design Tokens System
|
||||
|
||||
## Что это
|
||||
Единая система CSS-переменных (custom properties), которая обеспечивает консистентность дизайна. Все размеры, цвета, шрифты, анимации управляются из одного места.
|
||||
|
||||
## Архитектура
|
||||
|
||||
### File Structure
|
||||
```
|
||||
css/
|
||||
├── tokens/
|
||||
│ ├── color.css # Цветовая палитра
|
||||
│ ├── spacing.css # Масштаб отступов
|
||||
│ ├── typography.css # Масштаб шрифтов
|
||||
│ ├── motion.css # Параметры анимаций
|
||||
│ └── layout.css # Breakpoints, container sizes
|
||||
├── components/ # Компоненты используют tokens
|
||||
└── pages/ # Страницы используют components
|
||||
```
|
||||
|
||||
### CSS Custom Properties
|
||||
|
||||
```css
|
||||
/* tokens/color.css */
|
||||
:root {
|
||||
/* Brand colors */
|
||||
--color-brand-50: #f0f5ff;
|
||||
--color-brand-100: #e0eaff;
|
||||
--color-brand-200: #b9d0ff;
|
||||
--color-brand-400: #4f8fff;
|
||||
--color-brand-500: #2563eb;
|
||||
--color-brand-600: #1d4ed8;
|
||||
--color-brand-700: #1e40af;
|
||||
|
||||
/* Semantic colors */
|
||||
--color-bg: #0a0a0a;
|
||||
--color-surface: #111111;
|
||||
--color-surface-hover: #1a1a1a;
|
||||
--color-border: rgba(255,255,255,0.06);
|
||||
--color-border-hover: rgba(255,255,255,0.12);
|
||||
|
||||
/* Text colors */
|
||||
--color-text-primary: #f5f5f5;
|
||||
--color-text-secondary: #a0a0a0;
|
||||
--color-text-muted: #666666;
|
||||
|
||||
/* Accent colors */
|
||||
--color-accent: #6366f1;
|
||||
--color-accent-hover: #818cf8;
|
||||
--color-success: #10b981;
|
||||
--color-warning: #f59e0b;
|
||||
--color-danger: #ef4444;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* tokens/spacing.css */
|
||||
:root {
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
--space-16: 64px;
|
||||
--space-20: 80px;
|
||||
--space-24: 96px;
|
||||
--space-32: 128px;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* tokens/typography.css */
|
||||
:root {
|
||||
--font-primary: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
|
||||
/* Scale ratio: 1.25 (Major Third) */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
--text-5xl: 3rem;
|
||||
--text-6xl: 3.75rem;
|
||||
--text-7xl: 4.5rem;
|
||||
|
||||
/* Line heights */
|
||||
--leading-tight: 1.1;
|
||||
--leading-snug: 1.3;
|
||||
--leading-normal: 1.6;
|
||||
--leading-relaxed: 1.75;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* tokens/motion.css */
|
||||
:root {
|
||||
--ease-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
--ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
|
||||
--duration-fast: 150ms;
|
||||
--duration-normal: 300ms;
|
||||
--duration-slow: 500ms;
|
||||
--duration-xslow: 800ms;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* tokens/layout.css */
|
||||
:root {
|
||||
--container-sm: 640px;
|
||||
--container-md: 768px;
|
||||
--container-lg: 1024px;
|
||||
--container-xl: 1200px;
|
||||
--container-2xl: 1400px;
|
||||
|
||||
--breakpoint-sm: 640px;
|
||||
--breakpoint-md: 768px;
|
||||
--breakpoint-lg: 1024px;
|
||||
--breakpoint-xl: 1280px;
|
||||
}
|
||||
```
|
||||
|
||||
## CSS Reset с токенами
|
||||
|
||||
```css
|
||||
/* css/reset.css */
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 100%; /* 16px */
|
||||
scroll-behavior: smooth;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-primary);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text-primary);
|
||||
line-height: var(--leading-normal);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Typography with tokens */
|
||||
h1 { font-size: var(--text-5xl); line-height: var(--leading-tight); font-weight: 800; }
|
||||
h2 { font-size: var(--text-4xl); line-height: var(--leading-tight); font-weight: 700; }
|
||||
h3 { font-size: var(--text-2xl); line-height: var(--leading-snug); font-weight: 600; }
|
||||
|
||||
/* Spacing with tokens */
|
||||
.p-4 { padding: var(--space-4); }
|
||||
.p-6 { padding: var(--space-6); }
|
||||
.p-8 { padding: var(--space-8); }
|
||||
|
||||
.gap-4 { gap: var(--space-4); }
|
||||
.gap-6 { gap: var(--space-6); }
|
||||
```
|
||||
|
||||
## Component Tokens
|
||||
|
||||
```css
|
||||
/* tokens/components.css */
|
||||
:root {
|
||||
/* Buttons */
|
||||
--btn-radius: 10px;
|
||||
--btn-padding-y: var(--space-3);
|
||||
--btn-padding-x: var(--space-6);
|
||||
--btn-font-weight: 600;
|
||||
--btn-font-size: var(--text-base);
|
||||
|
||||
/* Cards */
|
||||
--card-radius: 16px;
|
||||
--card-padding: var(--space-8);
|
||||
--card-border: 1px solid var(--color-border);
|
||||
--card-bg: var(--color-surface);
|
||||
--card-hover-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
||||
|
||||
/* Inputs */
|
||||
--input-radius: 8px;
|
||||
--input-padding: var(--space-3) var(--space-4);
|
||||
--input-border: 1px solid var(--color-border);
|
||||
--input-focus-border: 1px solid var(--color-brand-500);
|
||||
}
|
||||
```
|
||||
|
||||
## Theming with Tokens
|
||||
|
||||
### Light Theme
|
||||
```css
|
||||
:root {
|
||||
--color-bg: #ffffff;
|
||||
--color-surface: #f5f5f5;
|
||||
--color-text-primary: #0a0a0a;
|
||||
--color-text-secondary: #555555;
|
||||
--color-border: rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--color-bg: #ffffff;
|
||||
--color-surface: #f5f5f5;
|
||||
--color-text-primary: #0a0a0a;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--color-bg: #0a0a0a;
|
||||
--color-surface: #111111;
|
||||
--color-text-primary: #f5f5f5;
|
||||
}
|
||||
```
|
||||
|
||||
### Dark Theme (default)
|
||||
```css
|
||||
:root {
|
||||
--color-bg: #0a0a0a;
|
||||
--color-surface: #111111;
|
||||
--color-text-primary: #f5f5f5;
|
||||
}
|
||||
```
|
||||
|
||||
## Utility Classes
|
||||
|
||||
```css
|
||||
/* tokens/utilities.css */
|
||||
.text-primary { color: var(--color-text-primary); }
|
||||
.text-secondary { color: var(--color-text-secondary); }
|
||||
.text-muted { color: var(--color-text-muted); }
|
||||
|
||||
.bg-surface { background: var(--color-surface); }
|
||||
.border-default { border: var(--color-border); }
|
||||
.border-hover:hover { border-color: var(--color-border-hover); }
|
||||
|
||||
.rounded-sm { border-radius: 4px; }
|
||||
.rounded { border-radius: 8px; }
|
||||
.rounded-md { border-radius: 12px; }
|
||||
.rounded-lg { border-radius: 16px; }
|
||||
.rounded-xl { border-radius: 24px; }
|
||||
.rounded-full { border-radius: 9999px; }
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
max-width: var(--container-xl);
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--space-6);
|
||||
}
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| ❌ Плохо | ✅ Хорошо |
|
||||
|----------|-----------|
|
||||
| `padding: 23px` | `padding: var(--space-6)` (24px) |
|
||||
| `color: #f0f0f0` | `color: var(--color-text-primary)` |
|
||||
| `font-size: 32px` | `font-size: var(--text-4xl)` |
|
||||
| `border-radius: 15px` | `border-radius: var(--card-radius)` |
|
||||
| `transition: 0.2s` | `transition: var(--duration-normal) var(--ease-out)` |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Один источник истины** — все токены в `tokens/`
|
||||
2. **Называйте по значению** — `--spacing-md`, `--color-primary`, не `--padding-1`, `--brand-1`
|
||||
3. **Без hardcoded значений в компонентах** — компоненты только токены
|
||||
4. **Планируйте на 20% вперёд** — если сейчас нужно 5 размеров spacing, добавьте ещё 2
|
||||
5. **Dokumentiruyte** — каждый токен должен быть описан
|
||||
|
||||
```css
|
||||
/* ✅ Хороший токен */
|
||||
:root {
|
||||
/** Отступ между карточками в сетке */
|
||||
--card-gap: 24px;
|
||||
/** Максимальная ширина контента */
|
||||
--max-width: 1200px;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,283 @@
|
||||
# Deterministic Generation — Seed-Based Layout Methodology
|
||||
|
||||
> Детерминированная генерация паттернов с использованием seeded PRNG.
|
||||
> Основано на [patterngen-oss](https://github.com/halfof8/patterngen-oss).
|
||||
|
||||
## Обзор
|
||||
|
||||
Каждая генерация паттернов **детерминирована** — одинаковый seed всегда даёт
|
||||
идентичный layout. Это позволяет:
|
||||
- Повторно воспроизводить удачные комбинации
|
||||
- Синхронизировать дизайн между Figma и PatternGen
|
||||
- Кэшировать результаты генерации
|
||||
- Делиться сценами через JSON с seed
|
||||
|
||||
---
|
||||
|
||||
## Seeded PRNG
|
||||
|
||||
### Алгоритм
|
||||
|
||||
```typescript
|
||||
class SeededRNG {
|
||||
private seed: number;
|
||||
|
||||
constructor(seed: number) {
|
||||
this.seed = seed & 0x7FFFFFFF; // 31-bit positive
|
||||
}
|
||||
|
||||
next(): number {
|
||||
// LCG: Linear Congruential Generator
|
||||
this.seed = (this.seed * 1103515245 + 12345) & 0x7FFFFFFF;
|
||||
return this.seed / 0x7FFFFFFF; // range [0, 1)
|
||||
}
|
||||
|
||||
nextInt(min: number, max: number): number {
|
||||
return Math.floor(this.next() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
pick<T>(array: T[]): T {
|
||||
return array[this.nextInt(0, array.length - 1)];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Seed из Figma
|
||||
|
||||
```json
|
||||
{
|
||||
"seed": 123456789
|
||||
}
|
||||
```
|
||||
|
||||
31-bit integer генерируется плагином Figma. Значение зашито в JSON
|
||||
и передается в PatternGen.
|
||||
|
||||
---
|
||||
|
||||
## Placement Engine
|
||||
|
||||
### Input parameters
|
||||
|
||||
| Параметр | Тип | Диапазон | Описание |
|
||||
|----------|-----|----------|----------|
|
||||
| `seed` | int | 0–2³¹-1 | Seed для PRNG |
|
||||
| `density` | int | 1–20 | Количество элементов |
|
||||
| `proximity` | int | 1–20 | Мин. расстояние до title (20 px grid units) |
|
||||
|
||||
### Placement algorithm
|
||||
|
||||
```
|
||||
1. Создать RNG из seed
|
||||
2. Для каждого элемента:
|
||||
a. Сгенерировать случайную позицию (x, y)
|
||||
b. Проверить proximity к существующим элементам и title
|
||||
c. Если too close → пересадить с тем же RNG state
|
||||
d. Иначе → записать позицию
|
||||
3. Для каждого элемента:
|
||||
a. Выбрать случайный pattern из доступных
|
||||
b. Выбрать случайную clipSide (top/bottom/left/right)
|
||||
c. Выбрать случайную анимацию
|
||||
d. Сгенерировать stagger delay
|
||||
```
|
||||
|
||||
### Stagger calculation
|
||||
|
||||
```typescript
|
||||
const staggerWindow = Math.max(0, Math.min(0.85, (stagger / 5) * 0.8));
|
||||
const staggeredStart = element.animDelay * staggerWindow * durationMs;
|
||||
const elemDur = durationMs * (1 - staggerWindow);
|
||||
const localMs = cycleMs - staggeredStart;
|
||||
const p = Math.max(0, Math.min(1, localMs / elemDur));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Animation Timeline
|
||||
|
||||
### Фаза 1: Title Reveal (0 — durationMs)
|
||||
|
||||
Title проявляется из центра по горизонтали (если wider) или вертикали (if taller).
|
||||
|
||||
```typescript
|
||||
const titleT = min(1, max(0, elapsedMs / durationMs));
|
||||
const titleEased = applyEasing(titleT, easing);
|
||||
|
||||
if (title.w >= title.h) {
|
||||
// wide title: half-reveal horizontally
|
||||
halfReveal = (title.w / 2) * titleEased;
|
||||
revealBox = {
|
||||
x: cx - halfReveal,
|
||||
y: title.y,
|
||||
w: halfReveal * 2,
|
||||
h: title.h
|
||||
};
|
||||
} else {
|
||||
// tall title: half-reveal vertically
|
||||
halfReveal = (title.h / 2) * titleEased;
|
||||
revealBox = {
|
||||
x: title.x,
|
||||
y: cy - halfReveal,
|
||||
w: title.w,
|
||||
h: halfReveal * 2
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Фаза 2: Pattern Reveal (durationMs — 2× durationMs)
|
||||
|
||||
Паттерны проявляются с stagger-задержкой.
|
||||
|
||||
### Фаза 3: Dots Loop (2× durationMs — 4× durationMs)
|
||||
|
||||
Точки пульсируют синусоидой 4000ms цикл.
|
||||
|
||||
```typescript
|
||||
const fadeIn = min(1, loopMs / 800);
|
||||
const wave = 0.5 + 0.5 * sin(2 * PI * dot.blinkSpeed * (loopMs / 1000) + dot.blinkPhase * 2 * PI);
|
||||
const pulse = 0.15 + 0.85 * wave;
|
||||
opacity = pulse * fadeIn;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color System
|
||||
|
||||
### Fixed Colors
|
||||
|
||||
```typescript
|
||||
const FIXED_COLORS = ['#000000', '#FFFFFF'];
|
||||
```
|
||||
|
||||
Черный и белый всегда доступны.
|
||||
|
||||
### Custom Colors
|
||||
|
||||
До 3 кастомных цветов добавляются через color picker.
|
||||
Color pairs для паттернов генерируются алгоритмически:
|
||||
|
||||
```typescript
|
||||
// Для каждого pair custom_color:
|
||||
bg = customColor;
|
||||
fg = generateComplementary(customColor);
|
||||
```
|
||||
|
||||
### SVG Colorization
|
||||
|
||||
```typescript
|
||||
function colorizeSvg(svgText: string, colors: ColorPair): string {
|
||||
return svgText
|
||||
.replace(/fill="white"/g, `fill="${colors.bg}"`)
|
||||
.replace(/fill="black"/g, `fill="${colors.fg}"`)
|
||||
.replace(/stroke="black"/g, `stroke="${colors.fg}"`);
|
||||
}
|
||||
```
|
||||
|
||||
SVG-паттерны хранят только `white` (фон) и `black` (foreground).
|
||||
Окрашивание происходит в runtime.
|
||||
|
||||
---
|
||||
|
||||
## Image Caching
|
||||
|
||||
```typescript
|
||||
const imageCache = new Map<string, HTMLImageElement>();
|
||||
|
||||
export function svgToImage(svgText: string): Promise<HTMLImageElement> {
|
||||
if (imageCache.has(svgText)) return Promise.resolve(imageCache.get(svgText)!);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
const blob = new Blob([svgText], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
img.onload = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
imageCache.set(svgText, img);
|
||||
resolve(img);
|
||||
};
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
reject(new Error('Failed to load SVG image'));
|
||||
};
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
export function clearImageCache() {
|
||||
imageCache.clear();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Export Pipeline
|
||||
|
||||
### Layer structure
|
||||
|
||||
```
|
||||
titles: durationMs (1×) — plays once
|
||||
patterns: durationMs (1×) — plays once
|
||||
dots: 4000ms loop — infinite loop
|
||||
```
|
||||
|
||||
### Export scale
|
||||
|
||||
- Canvas: 1920×1080
|
||||
- Export Scale: 4× → 7680×4320
|
||||
- FPS: 24
|
||||
- Format: PNG (RGBA, transparent background)
|
||||
|
||||
### Frame calculation
|
||||
|
||||
```typescript
|
||||
const totalFrames = Math.ceil((layerDuration / 1000) * FPS);
|
||||
const frameRatio = totalFrames > 1 ? frame / (totalFrames - 1) : 1;
|
||||
```
|
||||
|
||||
### Naming convention
|
||||
|
||||
```
|
||||
<sceneName>_<layer>_<frame>.png
|
||||
|
||||
Пример:
|
||||
MyTitle_titles_00000.png
|
||||
MyTitle_titles_00001.png
|
||||
MyTitle_patterns_00000.png
|
||||
MyTitle_dots_00000.png
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Использование determinism в дизайне
|
||||
|
||||
### Scenario 1: Figma → PatternGen sync
|
||||
|
||||
1. Экспортировать сцену из Figma (содержит seed)
|
||||
2. Загрузить в PatternGen
|
||||
3. Паттерны генерируются точно так же каждый раз
|
||||
4. Можно итерировать параметры и снова генерировать тот же layout
|
||||
|
||||
### Scenario 2: A/B testing
|
||||
|
||||
1. Создать базовую сцену с seed
|
||||
2. Экспортировать 10 раз с разными параметрами density/stagger
|
||||
3. Каждый раз layout идентичен, но timing разный
|
||||
4. Сравнить конверсию/визуальное впечатление
|
||||
|
||||
### Scenario 3: Team collaboration
|
||||
|
||||
1. Дизайнер делится scene JSON
|
||||
2. Коллега загружает и видит ту же генерацию
|
||||
3. Может менять цвета, паттерны, параметры
|
||||
4. Seed остаётся — layout predictable
|
||||
|
||||
---
|
||||
|
||||
## Референсы кода
|
||||
|
||||
- `src/core/rng.ts` — SeededRNG класс, LCG algorithm
|
||||
- `src/core/placement.ts` — Placement engine, proximity check
|
||||
- `src/engine/timeline.ts` — Phase 1/2/3, dots loop
|
||||
- `src/export/exporter.ts` — PNG export, frame calculation
|
||||
- `src/export/renderer.ts` — Canvas rendering per frame
|
||||
- `src/types.ts` — AnimationState, PatternAnimType
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: system/glassmorphism
|
||||
title: Glassmorphism & Glass Effects
|
||||
description: Frosted glass UI elements with blur and transparency.
|
||||
tags: [system, glass, blur, transparency, backdrop-filter]
|
||||
source: apple.com, stripe.com, raycast.com, linear.app
|
||||
---
|
||||
|
||||
# Glassmorphism
|
||||
|
||||
## CSS
|
||||
|
||||
```css
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 20px;
|
||||
}
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| ❌ Плохо | ✅ Хорошо |
|
||||
|----------|-----------|
|
||||
| `backdrop-filter` без fallback | `background: rgba()` fallback |
|
||||
| `blur(2px)` — не видно | `blur(20px)` |
|
||||
| Без `-webkit-` prefix | `-webkit-backdrop-filter` |
|
||||
@@ -0,0 +1,240 @@
|
||||
---
|
||||
name: system/noise-texture
|
||||
title: Noise & Grain Texture Overlay
|
||||
description: Subtle noise/grain overlay for premium tactile feel. Eliminates flat digital look.
|
||||
tags: [system, noise, texture, grain, tactile, premium]
|
||||
source: stripe.com, linear.app, brad.co, amppz.com
|
||||
---
|
||||
|
||||
# Noise & Grain Texture Overlay
|
||||
|
||||
## Что это
|
||||
Тонкая текстура шума (noise), наложенная поверх фона. Создаёт ощущение тактильности и устраняет "плоский" цифровой вид. Используется на Stripe, Linear, Branded.
|
||||
|
||||
## Почему это важно
|
||||
Без текстуры тёмный сайт выглядит плоским. Noise добавляет:
|
||||
- Глубину
|
||||
- Тактильность
|
||||
- "Дорогой" вид
|
||||
- Маскировку градиентных артефактов
|
||||
|
||||
## Pattern 1: CSS Noise (рекомендуется)
|
||||
|
||||
```html
|
||||
<div class="noise-overlay"></div>
|
||||
```
|
||||
|
||||
```css
|
||||
.noise-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
opacity: 0.03;
|
||||
mix-blend-mode: overlay;
|
||||
background-image: url('/noise.png');
|
||||
background-size: 256px 256px;
|
||||
}
|
||||
```
|
||||
|
||||
### Генерация noise.png
|
||||
```bash
|
||||
# Python — генерация noise текстуры
|
||||
python3 -c "
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
noise = np.random.randint(0, 255, (512, 512, 3), dtype=np.uint8)
|
||||
Image.fromarray(noise).save('/public/noise.png')
|
||||
"
|
||||
|
||||
# Или через CLI
|
||||
convert -size 256x256 plasma:random /public/noise.png
|
||||
```
|
||||
|
||||
## Pattern 2: SVG Noise Filter
|
||||
|
||||
```html
|
||||
<!-- SVG Noise Filter — не требует отдельного файла -->
|
||||
<svg style="position: absolute; width: 0; height: 0; overflow: hidden;">
|
||||
<defs>
|
||||
<filter id="noise">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>
|
||||
<feColorMatrix type="saturate" values="0"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
```
|
||||
|
||||
```css
|
||||
.noise-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
opacity: 0.04;
|
||||
filter: url(#noise);
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
```
|
||||
|
||||
## Pattern 3: CSS Radial Noise (без изображений)
|
||||
|
||||
```css
|
||||
.noise-css {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
opacity: 0.03;
|
||||
background:
|
||||
radial-gradient(circle, #fff 1px, transparent 1px),
|
||||
radial-gradient(circle, #fff 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
background-position: 0 0, 10px 10px;
|
||||
mix-blend-mode: overlay;
|
||||
animation: noise-shift 0.5s steps(1) infinite;
|
||||
}
|
||||
|
||||
@keyframes noise-shift {
|
||||
0% { transform: translate(0, 0); }
|
||||
25% { transform: translate(-5px, 5px); }
|
||||
50% { transform: translate(5px, -5px); }
|
||||
75% { transform: translate(-5px, -5px); }
|
||||
100% { transform: translate(5px, 5px); }
|
||||
}
|
||||
```
|
||||
|
||||
## Pattern 4: Gradient Noise Mix
|
||||
|
||||
```css
|
||||
.gradient-noise {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gradient-noise::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(99, 102, 241, 0.1), transparent),
|
||||
url('/noise.png');
|
||||
background-size: cover, 256px 256px;
|
||||
opacity: 0.8;
|
||||
pointer-events: none;
|
||||
}
|
||||
```
|
||||
|
||||
## Pattern 5: Noise on Cards/Sections
|
||||
|
||||
```html
|
||||
<section class="card-with-noise">
|
||||
<div class="noise-layer"></div>
|
||||
<div class="card-content">
|
||||
<h3>Контент карточки</h3>
|
||||
<p>Текст карточки...</p>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
```css
|
||||
.card-with-noise {
|
||||
position: relative;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.noise-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0.02;
|
||||
background-image: url('/noise.png');
|
||||
background-size: 256px 256px;
|
||||
pointer-events: none;
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 48px;
|
||||
}
|
||||
```
|
||||
|
||||
## Noise Intensity Levels
|
||||
|
||||
| Уровень | Opacity | Использование |
|
||||
|---------|---------|---------------|
|
||||
| `0.01` | Очень тихо | Фоновый шум на светлых сайтах |
|
||||
| `0.02–0.03` | Тихо | Тёмные фоны (стандарт) |
|
||||
| `0.04–0.06` | Средне | Градиенты, акцентные элементы |
|
||||
| `0.07+` | Громко | Текстуры, арт-проекты |
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| ❌ Плохо | ✅ Хорошо |
|
||||
|----------|-----------|
|
||||
| `opacity: 0.3` — слишком видно | `opacity: 0.02–0.04` |
|
||||
| `mix-blend-mode: normal` | `mix-blend-mode: overlay` |
|
||||
| Без `pointer-events: none` | `pointer-events: none` |
|
||||
| Без `z-index: 9999` | Всегда поверх контента |
|
||||
| Тяжёлый PNG (512x512+) | `256x256` оптимизированный |
|
||||
|
||||
## Performance
|
||||
|
||||
```css
|
||||
/* ✅ Good — CSS-only noise */
|
||||
.noise-css {
|
||||
background:
|
||||
radial-gradient(circle, #fff 1px, transparent 1px);
|
||||
/* GPU accelerated */
|
||||
}
|
||||
|
||||
/* ✅ Good — оптимизированный PNG */
|
||||
.noise-overlay {
|
||||
background-image: url('/noise-256.png');
|
||||
/* 256x256, < 5KB */
|
||||
}
|
||||
|
||||
/* ❌ Bad — тяжёлый SVG */
|
||||
.noise-overlay {
|
||||
filter: url('#noise-filter');
|
||||
/* Перерисовка всего viewport */
|
||||
}
|
||||
```
|
||||
|
||||
## Browser Support
|
||||
|
||||
```css
|
||||
.noise-overlay {
|
||||
background-image: url('/noise.png'); /* fallback */
|
||||
background-image: url('/noise.svg'); /* modern */
|
||||
/* Для очень старых браузеров */
|
||||
@supports (background: url('/noise.png')) {
|
||||
/* стандартное поведение */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CV Service Validation
|
||||
|
||||
```bash
|
||||
# Проверить visual hierarchy с noise
|
||||
curl -s -X POST http://localhost:8001/design/analyze \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"image_base64": "...", "full_analysis": false}' | python3 -c "
|
||||
import sys, json
|
||||
d = json.load(sys.stdin)
|
||||
color = d['dimensions'].get('color_harmony', {})
|
||||
print(f\"Color Harmony: {color.get('score', 'N/A')}/10\")
|
||||
"
|
||||
```
|
||||
|
||||
Ожидается: `color_harmony` > 6 (noise улучшает восприятие цвета).
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: system/responsive-patterns
|
||||
title: Responsive Design Patterns
|
||||
description: Breakpoints, mobile-first, container queries.
|
||||
tags: [system, responsive, mobile, container-queries]
|
||||
source: stripe.com, linear.app, brad.co, css-tricks.com
|
||||
---
|
||||
|
||||
# Responsive Patterns
|
||||
|
||||
## Breakpoints
|
||||
|
||||
```css
|
||||
:root {
|
||||
--breakpoint-sm: 640px;
|
||||
--breakpoint-md: 768px;
|
||||
--breakpoint-lg: 1024px;
|
||||
--breakpoint-xl: 1280px;
|
||||
}
|
||||
```
|
||||
|
||||
## Container Queries
|
||||
|
||||
```css
|
||||
@container (min-width: 600px) {
|
||||
.card { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| ❌ Плохо | ✅ Хорошо |
|
||||
|----------|-----------|
|
||||
| 1000px breakpoint | 768px / 1024px |
|
||||
| Fixed width | `max-width: 1200px` |
|
||||
Reference in new issue
Block a user