dsh-desktop-kit 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +152 -2
- package/client.js +4101 -0
- package/cordis.patch.yml +5 -0
- package/docs/ARCHITECTURE.md +114 -0
- package/docs/COMPATIBILITY.md +61 -0
- package/docs/INSTALL.md +84 -0
- package/docs/PRIVACY.md +50 -0
- package/docs/ROADMAP.md +57 -0
- package/docs/TOKENS.md +118 -0
- package/docs/TROUBLESHOOTING.md +68 -0
- package/docs/images/hero.png +0 -0
- package/index.js +84 -0
- package/package.json +70 -4
- package/plugins/dsh-gkb-ru-ui/cordis.patch.yml +6 -0
- package/plugins/dsh-gkb-ru-ui/lib/client.js +1331 -0
- package/plugins/dsh-gkb-ru-ui/lib/index.js +186 -0
- package/plugins/dsh-gkb-ru-ui/lib/skills.js +570 -0
- package/plugins/dsh-gkb-ru-ui/package.json +28 -0
- package/plugins/dsh-gkb-ru-ui/screenshots/01-home-tiles-1440-light.png +0 -0
- package/plugins/dsh-gkb-ru-ui/screenshots/02-skills-1440-light.png +0 -0
- package/plugins/dsh-gkb-ru-ui/screenshots/03-skills-editor-1440-light.png +0 -0
- package/plugins/dsh-gkb-ru-ui/screenshots/04-skills-1440-dark.png +0 -0
- package/plugins/dsh-gkb-ru-ui/screenshots/05-skills-390-light.png +0 -0
- package/plugins/dsh-gkb-ru-ui/screenshots/06-home-tiles-1440-dark.png +0 -0
- package/plugins/dsh-peak-guard/README.md +70 -0
- package/plugins/dsh-peak-guard/client.js +741 -0
- package/plugins/dsh-peak-guard/cordis.patch.yml +5 -0
- package/plugins/dsh-peak-guard/icon.svg +12 -0
- package/plugins/dsh-peak-guard/index.js +2 -0
- package/plugins/dsh-peak-guard/locale/en.json +6 -0
- package/plugins/dsh-peak-guard/locale/ru.json +6 -0
- package/plugins/dsh-peak-guard/locale/zh.json +6 -0
- package/plugins/dsh-peak-guard/package.json +33 -0
- package/plugins/dsh-peak-guard/screenshots/01-real-time-peak-start.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/02-real-time-peak-end.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/03-peak-card-1440-light.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/04-peak-card-1440-dark.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/05-peak-card-390-light.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/05-peak-card-768-light.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/06-peak-card-390-light.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/07-live-preview-check.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/07-pre-peak-1440-light.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/08-peak-start-1440-light.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/09-peak-end-1440-light.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/10-pre-peak-card-1440-light.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/11-pre-peak-card-1440-dark.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/12-pre-peak-card-390-light.png +0 -0
- package/plugins/dsh-peak-guard/screenshots/qa-report.json +74 -0
- package/plugins/dsh-profile-transfer/README.md +62 -0
- package/plugins/dsh-profile-transfer/bootstrap/01-INSTALL.cmd +7 -0
- package/plugins/dsh-profile-transfer/bootstrap/README-ARCHIVE.md +54 -0
- package/plugins/dsh-profile-transfer/bootstrap/README-/320/243/320/241/320/242/320/220/320/235/320/236/320/222/320/232/320/220.md +42 -0
- package/plugins/dsh-profile-transfer/bootstrap/install-on-clean-machine.ps1 +119 -0
- package/plugins/dsh-profile-transfer/bootstrap//320/243/320/241/320/242/320/220/320/235/320/236/320/222/320/230/320/242/320/254.cmd +5 -0
- package/plugins/dsh-profile-transfer/client.js +806 -0
- package/plugins/dsh-profile-transfer/cordis.patch.yml +3 -0
- package/plugins/dsh-profile-transfer/icon.svg +13 -0
- package/plugins/dsh-profile-transfer/index.js +375 -0
- package/plugins/dsh-profile-transfer/locale/en.json +6 -0
- package/plugins/dsh-profile-transfer/locale/ru.json +6 -0
- package/plugins/dsh-profile-transfer/locale/zh.json +6 -0
- package/plugins/dsh-profile-transfer/package.json +36 -0
- package/plugins/dsh-profile-transfer/screenshots/01-settings-transfer-1440-light.png +0 -0
- package/plugins/dsh-profile-transfer/screenshots/02-settings-transfer-preview-1440-light.png +0 -0
- package/plugins/dsh-profile-transfer/screenshots/03-settings-transfer-1440-dark.png +0 -0
- package/plugins/dsh-profile-transfer/screenshots/04-settings-transfer-390-light.png +0 -0
- package/plugins/dsh-profile-transfer/screenshots/05-settings-transfer-768-light.png +0 -0
- package/plugins/dsh-profile-transfer/screenshots/06-settings-transfer-390-dark.png +0 -0
- package/plugins/dsh-profile-transfer/screenshots/07-settings-transfer-export-result.png +0 -0
- package/plugins/dsh-profile-transfer/screenshots/08-settings-transfer-workspaces-1440-light.png +0 -0
- package/plugins/dsh-profile-transfer/screenshots/09-settings-transfer-workspaces-1440-dark.png +0 -0
- package/plugins/dsh-profile-transfer/screenshots/10-settings-transfer-workspaces-390-light.png +0 -0
- package/plugins/dsh-profile-transfer/transfer.js +1004 -0
- package/plugins/dsh-project-memory/README.md +71 -0
- package/plugins/dsh-project-memory/cordis.patch.yml +4 -0
- package/plugins/dsh-project-memory/icon.svg +12 -0
- package/plugins/dsh-project-memory/index.js +1240 -0
- package/plugins/dsh-project-memory/locale/en.json +6 -0
- package/plugins/dsh-project-memory/locale/ru.json +6 -0
- package/plugins/dsh-project-memory/locale/zh.json +6 -0
- package/plugins/dsh-project-memory/package.json +26 -0
- package/plugins/dsh-session-move/README.md +67 -0
- package/plugins/dsh-session-move/client.js +719 -0
- package/plugins/dsh-session-move/cordis.patch.yml +5 -0
- package/plugins/dsh-session-move/icon.svg +14 -0
- package/plugins/dsh-session-move/index.js +497 -0
- package/plugins/dsh-session-move/locale/en.json +6 -0
- package/plugins/dsh-session-move/locale/ru.json +6 -0
- package/plugins/dsh-session-move/locale/zh.json +6 -0
- package/plugins/dsh-session-move/package.json +31 -0
- package/plugins/dsh-session-move/screenshots/01-row-menu-1440-light.png +0 -0
- package/plugins/dsh-session-move/screenshots/02-dialog-1440-light.png +0 -0
- package/plugins/dsh-session-move/screenshots/03-dialog-390-light.png +0 -0
- package/plugins/dsh-session-move/screenshots/04-dialog-1440-dark.png +0 -0
- package/plugins/dsh-session-move/screenshots/05-drag-highlight-1440-light.png +0 -0
- package/plugins/dsh-session-move/screenshots/06-drop-ungrouped-highlight.png +0 -0
- package/plugins/dsh-session-move/screenshots/07-after-drop-ungrouped.png +0 -0
- package/plugins/dsh-session-move/screenshots/08-move-dialog.png +0 -0
- package/plugins/dsh-session-move/screenshots/09-moved-to-second-workspace.png +0 -0
- package/plugins/dsh-session-move/screenshots/qa-report.json +62 -0
- package/plugins/dsh-skill-autopilot/cordis.patch.yml +3 -0
- package/plugins/dsh-skill-autopilot/index.js +444 -0
- package/plugins/dsh-skill-autopilot/package.json +19 -0
- package/plugins/dsh-token-lab/cordis.patch.yml +3 -0
- package/plugins/dsh-token-lab/lib/client.js +504 -0
- package/plugins/dsh-token-lab/lib/index.js +275 -0
- package/plugins/dsh-token-lab/package.json +26 -0
- package/plugins/dsh-token-lab/screenshots/01-token-lab-1440-light.png +0 -0
- package/plugins/dsh-token-lab/screenshots/02-token-lab-1440-dark.png +0 -0
- package/skills/code-review-checklist/SKILL.md +55 -0
- package/skills/content-writing/README.md +87 -0
- package/skills/content-writing/SKILL.md +85 -0
- package/skills/content-writing/assets/checklists/fact-check.md +53 -0
- package/skills/content-writing/assets/checklists/publish.md +65 -0
- package/skills/content-writing/assets/checklists/social.md +40 -0
- package/skills/content-writing/assets/templates/article.md +47 -0
- package/skills/content-writing/assets/templates/brief.md +44 -0
- package/skills/content-writing/assets/templates/doctor-card.md +41 -0
- package/skills/content-writing/assets/templates/image-prompts.md +128 -0
- package/skills/content-writing/assets/templates/post-telegram.md +36 -0
- package/skills/content-writing/assets/templates/post-vk.md +42 -0
- package/skills/content-writing/assets/templates/press-release.md +56 -0
- package/skills/content-writing/assets/templates/science-pop-note.md +55 -0
- package/skills/content-writing/assets/templates/service-card.md +46 -0
- package/skills/content-writing/references/anti-ai-style.md +248 -0
- package/skills/content-writing/references/editing.md +185 -0
- package/skills/content-writing/references/fact-checking.md +205 -0
- package/skills/content-writing/references/government-medical.md +246 -0
- package/skills/content-writing/references/images.md +212 -0
- package/skills/content-writing/references/journalism.md +252 -0
- package/skills/content-writing/references/process.md +123 -0
- package/skills/content-writing/references/quick-recipes.md +99 -0
- package/skills/content-writing/references/rules.md +85 -0
- package/skills/content-writing/references/science-popular.md +218 -0
- package/skills/content-writing/references/seo-content.md +172 -0
- package/skills/content-writing/references/social-media.md +253 -0
- package/skills/content-writing/references/sources.md +49 -0
- package/skills/content-writing/references/templates.md +291 -0
- package/skills/context-economy/SKILL.md +60 -0
- package/skills/context-economy/references/long-session.md +35 -0
- package/skills/context-economy/references/token-math.md +42 -0
- package/skills/context-economy/references/tool-recipes.md +34 -0
- package/skills/database-safety/SKILL.md +47 -0
- package/skills/debugging-playbook/SKILL.md +42 -0
- package/skills/design-critique/SKILL.md +65 -0
- package/skills/design-critique/references/a11y.md +25 -0
- package/skills/design-critique/references/diagnoses.md +33 -0
- package/skills/design-critique/references/quick-tests.md +30 -0
- package/skills/design-critique/references/report-template.md +63 -0
- package/skills/design-critique/references/rubric-fast.md +73 -0
- package/skills/dev-workflow/SKILL.md +48 -0
- package/skills/dev-workflow/references/definition-of-done.md +44 -0
- package/skills/dev-workflow/references/memory-and-boundaries.md +31 -0
- package/skills/dev-workflow/references/stages.md +67 -0
- package/skills/git-and-ci/SKILL.md +60 -0
- package/skills/pg-upgrade-verify/SKILL.md +67 -0
- package/skills/project-deep-dive/SKILL.md +45 -0
- package/skills/project-deep-dive/references/recon-commands.md +90 -0
- package/skills/project-deep-dive/references/section-templates.md +104 -0
- package/skills/project-deep-dive/references/tool-and-process.md +86 -0
- package/skills/project-memory-maintenance/SKILL.md +55 -0
- package/skills/project-memory-maintenance/references/api-and-checklist.md +99 -0
- package/skills/project-memory-maintenance/references/journal-template.md +50 -0
- package/skills/project-memory-maintenance/references/section-rules.md +59 -0
- package/skills/release-and-deploy/SKILL.md +53 -0
- package/skills/requirements-and-planning/SKILL.md +49 -0
- package/skills/requirements-and-planning/references/antipatterns-and-checklist.md +20 -0
- package/skills/requirements-and-planning/references/process-steps.md +52 -0
- package/skills/requirements-and-planning/references/templates-and-estimates.md +29 -0
- package/skills/security-review/SKILL.md +40 -0
- package/skills/seo/README.md +60 -0
- package/skills/seo/SKILL.md +77 -0
- package/skills/seo/assets/README.md +66 -0
- package/skills/seo/assets/scripts/LICENSE-playwright-core.txt +202 -0
- package/skills/seo/assets/scripts/README.md +179 -0
- package/skills/seo/assets/scripts/lib/args.mjs +105 -0
- package/skills/seo/assets/scripts/lib/browser.mjs +620 -0
- package/skills/seo/assets/scripts/lib/zip.mjs +191 -0
- package/skills/seo/assets/scripts/seo-audit.mjs +1675 -0
- package/skills/seo/assets/snippets/jsonld-breadcrumb.json +30 -0
- package/skills/seo/assets/snippets/jsonld-faq.json +39 -0
- package/skills/seo/assets/snippets/jsonld-medical-clinic.json +49 -0
- package/skills/seo/assets/snippets/jsonld-medical-webpage.json +33 -0
- package/skills/seo/assets/snippets/jsonld-organization.json +74 -0
- package/skills/seo/assets/snippets/jsonld-physician.json +46 -0
- package/skills/seo/assets/snippets/meta.blade.php +61 -0
- package/skills/seo/assets/snippets/robots.txt +61 -0
- package/skills/seo/assets/snippets/sitemap-urlset-example.xml +72 -0
- package/skills/seo/assets/snippets/sitemap.xml +36 -0
- package/skills/seo/references/antipatterns.md +66 -0
- package/skills/seo/references/audit-checklist.md +319 -0
- package/skills/seo/references/content-seo.md +204 -0
- package/skills/seo/references/eeat-medical.md +219 -0
- package/skills/seo/references/measurement.md +250 -0
- package/skills/seo/references/on-page.md +214 -0
- package/skills/seo/references/page-templates.md +56 -0
- package/skills/seo/references/stack-recipes.md +695 -0
- package/skills/seo/references/structured-data.md +487 -0
- package/skills/seo/references/technical-seo.md +296 -0
- package/skills/service-desk-conventions/SKILL.md +52 -0
- package/skills/service-desk-db-safety/SKILL.md +63 -0
- package/skills/service-desk-release/SKILL.md +61 -0
- package/skills/service-desk-testing/SKILL.md +61 -0
- package/skills/service-desk-ui/SKILL.md +52 -0
- package/skills/skills-router/SKILL.md +53 -0
- package/skills/skills-router/references/applying-skills.md +25 -0
- package/skills/skills-router/references/routing-matrix.md +52 -0
- package/skills/testing-strategy/SKILL.md +52 -0
- package/skills/ui-design-system/SKILL.md +50 -0
- package/skills/ui-design-system/references/accessibility-and-responsive.md +50 -0
- package/skills/ui-design-system/references/tokens-and-components.md +67 -0
- package/skills/ui-design-system/references/visual-qa.md +44 -0
- package/skills/web-research/SKILL.md +47 -0
- package/skills/web-research/references/answer-and-citations.md +60 -0
- package/skills/web-research/references/query-recipes.md +44 -0
- package/skills/wow-design/SKILL.md +89 -0
- package/skills/wow-design/assets/kit/README.md +195 -0
- package/skills/wow-design/assets/kit/assets/css/base.css +158 -0
- package/skills/wow-design/assets/kit/assets/css/components.css +1001 -0
- package/skills/wow-design/assets/kit/assets/css/fonts.css +73 -0
- package/skills/wow-design/assets/kit/assets/css/pages.css +737 -0
- package/skills/wow-design/assets/kit/assets/css/tokens.css +218 -0
- package/skills/wow-design/assets/kit/assets/fonts/LICENSE-golos-text.txt +93 -0
- package/skills/wow-design/assets/kit/assets/fonts/LICENSE-lora.txt +93 -0
- package/skills/wow-design/assets/kit/assets/fonts/LICENSE-unbounded.txt +93 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-700-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-700-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-400-italic.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-latin-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-latin-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-latin-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/js/kit.js +550 -0
- package/skills/wow-design/assets/kit/pages/dashboard.html +539 -0
- package/skills/wow-design/assets/kit/pages/hospital.html +430 -0
- package/skills/wow-design/assets/kit/showcase.html +1164 -0
- package/skills/wow-design/assets/scripts/LICENSE-axe-core.txt +362 -0
- package/skills/wow-design/assets/scripts/LICENSE-playwright-core.txt +202 -0
- package/skills/wow-design/assets/scripts/README.md +312 -0
- package/skills/wow-design/assets/scripts/audit.mjs +1200 -0
- package/skills/wow-design/assets/scripts/capture.mjs +337 -0
- package/skills/wow-design/assets/scripts/package.json +30 -0
- package/skills/wow-design/references/anti-patterns.md +252 -0
- package/skills/wow-design/references/critique-rubric.md +262 -0
- package/skills/wow-design/references/design-brief-template.md +48 -0
- package/skills/wow-design/references/inspiration.md +203 -0
- package/skills/wow-design/references/presets.md +99 -0
- package/skills/wow-design/references/principles.md +371 -0
- package/skills/wow-design/references/quick-presets.md +50 -0
- package/skills/wow-design/references/report-template.md +21 -0
- package/skills/wow-design/references/stack-recipes.md +96 -0
- package/skills/wow-design/references/visual-qa.md +82 -0
- package/skills/wow-design/references/wow-levers.md +43 -0
- package/tools/skill-cost.mjs +76 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* capture.mjs — скриншоты страниц для визуального QA.
|
|
4
|
+
*
|
|
5
|
+
* Подключается к уже запущенному headless Chrome по CDP (по умолчанию
|
|
6
|
+
* http://127.0.0.1:9333, можно переопределить --cdp / CDP_URL). Если CDP
|
|
7
|
+
* недоступен — сам запускает chromium из ms-playwright и подключается к нему.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node capture.mjs <путь-к-html|url> --name <name> [--out <dir>]
|
|
11
|
+
* [--viewports desktop,tablet,mobile] [--themes light,dark] [--full]
|
|
12
|
+
* [--selector <css>] [--dpr 1] [--preset aurora] [--serve] [--json]
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { parseArgs, csv, safeFileName, UsageError, printHelpAndExit } from './lib/args.mjs';
|
|
19
|
+
import {
|
|
20
|
+
connectChrome,
|
|
21
|
+
disposeChrome,
|
|
22
|
+
resolveTarget,
|
|
23
|
+
startStaticServer,
|
|
24
|
+
openPage,
|
|
25
|
+
closePage,
|
|
26
|
+
gotoPage,
|
|
27
|
+
wireDiagnostics,
|
|
28
|
+
addThemeInitScript,
|
|
29
|
+
preparePage,
|
|
30
|
+
waitForFonts,
|
|
31
|
+
parseViewportList,
|
|
32
|
+
parseThemeList,
|
|
33
|
+
} from './lib/browser.mjs';
|
|
34
|
+
|
|
35
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
const WORKSPACE = path.resolve(HERE, '..', '..');
|
|
37
|
+
const DEFAULT_OUT = path.join(WORKSPACE, 'tmp', 'qa-artifacts');
|
|
38
|
+
|
|
39
|
+
const HELP = `
|
|
40
|
+
capture.mjs — PNG-скриншоты страницы в разных viewport и темах через headless Chrome (CDP).
|
|
41
|
+
|
|
42
|
+
Использование:
|
|
43
|
+
node tools/design-qa/capture.mjs <путь-к-html|url> --name <name> [опции]
|
|
44
|
+
|
|
45
|
+
Опции:
|
|
46
|
+
--name <name> Имя префикса файлов (по умолчанию — имя html-файла)
|
|
47
|
+
--out <dir> Куда складывать PNG и shots.json (по умолчанию tmp/qa-artifacts)
|
|
48
|
+
--viewports <list> desktop,tablet,mobile или <ширина>x<высота> (по умолчанию все три)
|
|
49
|
+
--themes <list> light,dark (по умолчанию обе)
|
|
50
|
+
--full / --no-full Полная страница (по умолчанию включено; --no-full — только видимая часть)
|
|
51
|
+
--selector <css> Снимать только первый элемент, подходящий под CSS-селектор
|
|
52
|
+
--dpr <n> devicePixelRatio, 1..4 (по умолчанию 1)
|
|
53
|
+
--preset <id> Выставить data-preset на <html> (aurora|graphite|editorial|warm|swiss)
|
|
54
|
+
--serve Отдавать страницу по http://127.0.0.1 (нужно для <script type="module">)
|
|
55
|
+
--cdp <url> CDP-эндпоинт (по умолчанию env CDP_URL или http://127.0.0.1:9333)
|
|
56
|
+
--timeout <ms> Таймаут загрузки страницы (по умолчанию 30000)
|
|
57
|
+
--json Печатать манифест в stdout как JSON (логи — в stderr)
|
|
58
|
+
-h, --help Эта справка
|
|
59
|
+
|
|
60
|
+
Примеры:
|
|
61
|
+
node tools/design-qa/capture.mjs projects/design-kit/showcase.html --name showcase
|
|
62
|
+
node tools/design-qa/capture.mjs tools/design-qa/fixtures/test.html --viewports mobile --themes dark
|
|
63
|
+
node tools/design-qa/capture.mjs projects/design-kit/pages/dashboard.html --name dashboard --selector "main"
|
|
64
|
+
|
|
65
|
+
Коды выхода: 0 — все скриншоты сняты, 1 — ошибка/частичный сбой (см. shots.json).
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
async function main() {
|
|
69
|
+
const args = parseArgs(process.argv.slice(2), {
|
|
70
|
+
booleans: ['help', 'json', 'full', 'serve', 'motion', 'quiet'],
|
|
71
|
+
values: ['name', 'out', 'viewports', 'themes', 'selector', 'dpr', 'preset', 'cdp', 'timeout'],
|
|
72
|
+
aliases: { h: 'help' },
|
|
73
|
+
defaults: {
|
|
74
|
+
help: false,
|
|
75
|
+
json: false,
|
|
76
|
+
full: true,
|
|
77
|
+
serve: false,
|
|
78
|
+
motion: false,
|
|
79
|
+
quiet: false,
|
|
80
|
+
name: undefined,
|
|
81
|
+
out: undefined,
|
|
82
|
+
viewports: 'desktop,tablet,mobile',
|
|
83
|
+
themes: 'light,dark',
|
|
84
|
+
selector: undefined,
|
|
85
|
+
dpr: '1',
|
|
86
|
+
preset: undefined,
|
|
87
|
+
cdp: undefined,
|
|
88
|
+
timeout: '30000',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (args.help) printHelpAndExit(HELP);
|
|
93
|
+
const log = args.json ? (...a) => console.error(...a) : (...a) => console.log(...a);
|
|
94
|
+
|
|
95
|
+
const target = args._[0];
|
|
96
|
+
if (!target) throw new UsageError('Не указан путь к HTML-файлу или URL (см. --help)');
|
|
97
|
+
|
|
98
|
+
const viewports = parseViewportList(args.viewports);
|
|
99
|
+
const themes = parseThemeList(args.themes);
|
|
100
|
+
const dpr = parseNumber(args.dpr, '--dpr', 1, 4);
|
|
101
|
+
const timeout = parseNumber(args.timeout, '--timeout', 1000, 180_000);
|
|
102
|
+
const selector = args.selector ? String(args.selector) : null;
|
|
103
|
+
const preset = args.preset ? String(args.preset) : null;
|
|
104
|
+
const reducedMotion = args.motion ? 'no-preference' : 'reduce';
|
|
105
|
+
|
|
106
|
+
const resolved = resolveTarget(String(target), { serve: args.serve });
|
|
107
|
+
const name = safeFileName(args.name || defaultName(resolved), 'page');
|
|
108
|
+
const outDir = path.resolve(process.cwd(), args.out || DEFAULT_OUT);
|
|
109
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
110
|
+
|
|
111
|
+
const startedAt = new Date();
|
|
112
|
+
const t0 = Date.now();
|
|
113
|
+
log(`[capture] ${resolved.source}`);
|
|
114
|
+
log(`[capture] имя: ${name}; viewports: ${viewports.map((v) => `${v.id}(${v.width}x${v.height})`).join(', ')}; темы: ${themes.join(', ')}; dpr: ${dpr}`);
|
|
115
|
+
log(`[capture] каталог: ${outDir}`);
|
|
116
|
+
|
|
117
|
+
let conn = null;
|
|
118
|
+
let server = null;
|
|
119
|
+
const manifest = {
|
|
120
|
+
tool: 'design-qa/capture',
|
|
121
|
+
version: 1,
|
|
122
|
+
name,
|
|
123
|
+
source: resolved.source,
|
|
124
|
+
url: resolved.url,
|
|
125
|
+
targetKind: resolved.kind,
|
|
126
|
+
startedAt: startedAt.toISOString(),
|
|
127
|
+
finishedAt: null,
|
|
128
|
+
durationMs: null,
|
|
129
|
+
browser: null,
|
|
130
|
+
options: {
|
|
131
|
+
viewports: viewports.map((v) => ({ id: v.id, width: v.width, height: v.height })),
|
|
132
|
+
themes,
|
|
133
|
+
fullPage: selector ? false : Boolean(args.full),
|
|
134
|
+
selector,
|
|
135
|
+
dpr,
|
|
136
|
+
preset,
|
|
137
|
+
reducedMotion,
|
|
138
|
+
serve: Boolean(args.serve),
|
|
139
|
+
},
|
|
140
|
+
shots: [],
|
|
141
|
+
consoleErrors: [],
|
|
142
|
+
pageErrors: [],
|
|
143
|
+
failedRequests: [],
|
|
144
|
+
totals: null,
|
|
145
|
+
ok: false,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
conn = await connectChrome({ cdpUrl: args.cdp || undefined, log });
|
|
150
|
+
manifest.browser = {
|
|
151
|
+
endpoint: conn.endpoint,
|
|
152
|
+
launched: conn.launched,
|
|
153
|
+
browser: conn.version?.Browser || conn.version?.browser || null,
|
|
154
|
+
};
|
|
155
|
+
log(`[capture] браузер: ${manifest.browser.browser || '?'} (${conn.launched ? 'запущен fallback' : 'CDP ' + conn.endpoint})`);
|
|
156
|
+
|
|
157
|
+
if (args.serve) {
|
|
158
|
+
if (!resolved.abs) throw new UsageError('--serve поддерживается только для локальных html-файлов');
|
|
159
|
+
server = await startStaticServer(process.cwd());
|
|
160
|
+
const rel = path.relative(process.cwd(), resolved.abs).split(path.sep).join('/');
|
|
161
|
+
resolved.url = `${server.origin}/${rel}`;
|
|
162
|
+
manifest.url = resolved.url;
|
|
163
|
+
log(`[capture] локальный сервер: ${server.origin}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const vp of viewports) {
|
|
167
|
+
for (const theme of themes) {
|
|
168
|
+
const shot = {
|
|
169
|
+
id: `${vp.id}-${theme}`,
|
|
170
|
+
viewport: vp.id,
|
|
171
|
+
width: vp.width,
|
|
172
|
+
height: vp.height,
|
|
173
|
+
theme,
|
|
174
|
+
dpr,
|
|
175
|
+
fullPage: selector ? false : Boolean(args.full),
|
|
176
|
+
selector: selector || null,
|
|
177
|
+
file: `${name}-${vp.id}-${theme}.png`,
|
|
178
|
+
path: path.join(outDir, `${name}-${vp.id}-${theme}.png`),
|
|
179
|
+
bytes: 0,
|
|
180
|
+
widthPx: 0,
|
|
181
|
+
heightPx: 0,
|
|
182
|
+
page: null,
|
|
183
|
+
fonts: null,
|
|
184
|
+
consoleErrors: [],
|
|
185
|
+
pageErrors: [],
|
|
186
|
+
failedRequests: [],
|
|
187
|
+
error: null,
|
|
188
|
+
elapsedMs: 0,
|
|
189
|
+
};
|
|
190
|
+
const ts = Date.now();
|
|
191
|
+
try {
|
|
192
|
+
const handle = await openPage(conn.browser, { viewport: vp, dpr, theme, reducedMotion });
|
|
193
|
+
const page = handle.page;
|
|
194
|
+
const diag = wireDiagnostics(page);
|
|
195
|
+
try {
|
|
196
|
+
await addThemeInitScript(page, { theme, preset });
|
|
197
|
+
await gotoPage(page, resolved.url, { timeout });
|
|
198
|
+
const pageMetrics = await preparePage(page, { theme, preset, animations: true, scroll: true });
|
|
199
|
+
const fonts = await waitForFonts(page);
|
|
200
|
+
await takeScreenshot(page, shot, { selector, fullPage: shot.fullPage });
|
|
201
|
+
const stat = fs.statSync(shot.path);
|
|
202
|
+
shot.bytes = stat.size;
|
|
203
|
+
shot.page = pageMetrics;
|
|
204
|
+
shot.fonts = { status: fonts.status, size: fonts.size };
|
|
205
|
+
} finally {
|
|
206
|
+
shot.consoleErrors = diag.consoleErrors.slice();
|
|
207
|
+
shot.pageErrors = diag.pageErrors.slice();
|
|
208
|
+
shot.failedRequests = diag.failedRequests.slice();
|
|
209
|
+
await closePage(handle);
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
shot.error = err?.isUsageError ? err.message : `${err?.name || 'Error'}: ${err?.message || err}`;
|
|
213
|
+
log(`[capture] ✖ ${shot.id}: ${shot.error}`);
|
|
214
|
+
}
|
|
215
|
+
shot.elapsedMs = Date.now() - ts;
|
|
216
|
+
manifest.shots.push(shot);
|
|
217
|
+
manifest.consoleErrors.push(...shot.consoleErrors.map((e) => ({ shot: shot.id, ...e })));
|
|
218
|
+
manifest.pageErrors.push(...shot.pageErrors.map((e) => ({ shot: shot.id, ...e })));
|
|
219
|
+
manifest.failedRequests.push(...shot.failedRequests.map((e) => ({ shot: shot.id, ...e })));
|
|
220
|
+
|
|
221
|
+
if (!shot.error) {
|
|
222
|
+
const kb = (shot.bytes / 1024).toFixed(1);
|
|
223
|
+
const dims = shot.widthPx && shot.heightPx ? `${shot.widthPx}×${shot.heightPx}px` : '?';
|
|
224
|
+
log(`[capture] ✔ ${shot.id}: ${dims}, ${kb} КБ → ${path.relative(process.cwd(), shot.path)}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
manifest.fatalError = err?.message || String(err);
|
|
230
|
+
log(`[capture] ОШИБКА: ${manifest.fatalError}`);
|
|
231
|
+
} finally {
|
|
232
|
+
if (server) await server.close().catch(() => {});
|
|
233
|
+
if (conn) await disposeChrome(conn);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
manifest.finishedAt = new Date().toISOString();
|
|
237
|
+
manifest.durationMs = Date.now() - t0;
|
|
238
|
+
const okCount = manifest.shots.filter((s) => !s.error).length;
|
|
239
|
+
manifest.totals = {
|
|
240
|
+
shots: manifest.shots.length,
|
|
241
|
+
ok: okCount,
|
|
242
|
+
failed: manifest.shots.length - okCount,
|
|
243
|
+
consoleErrors: manifest.consoleErrors.length,
|
|
244
|
+
pageErrors: manifest.pageErrors.length,
|
|
245
|
+
failedRequests: manifest.failedRequests.length,
|
|
246
|
+
};
|
|
247
|
+
manifest.ok = !manifest.fatalError && manifest.shots.length > 0 && manifest.totals.failed === 0;
|
|
248
|
+
|
|
249
|
+
const manifestPath = path.join(outDir, 'shots.json');
|
|
250
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
251
|
+
// Персональная копия на случай параллельных прогонов (shots.json перезаписывается каждым запуском).
|
|
252
|
+
const perRunPath = path.join(outDir, `${name}-shots.json`);
|
|
253
|
+
fs.writeFileSync(perRunPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
254
|
+
log(
|
|
255
|
+
`[capture] итог: ${okCount}/${manifest.shots.length} скриншотов, ошибок консоли ${manifest.totals.consoleErrors}, ` +
|
|
256
|
+
`pageerror ${manifest.totals.pageErrors}, сбоев сети ${manifest.totals.failedRequests}`,
|
|
257
|
+
);
|
|
258
|
+
log(`[capture] манифест: ${manifestPath} (копия: ${perRunPath})`);
|
|
259
|
+
|
|
260
|
+
if (args.json) process.stdout.write(JSON.stringify(manifest, null, 2) + '\n');
|
|
261
|
+
process.exitCode = manifest.ok ? 0 : 1;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function parseNumber(raw, option, min, max) {
|
|
265
|
+
const n = Number(raw);
|
|
266
|
+
if (!Number.isFinite(n) || n < min || n > max) {
|
|
267
|
+
throw new UsageError(`${option}: ожидается число ${min}..${max}, получено «${raw}»`);
|
|
268
|
+
}
|
|
269
|
+
return n;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function defaultName(resolved) {
|
|
273
|
+
if (resolved.abs) return path.basename(resolved.abs, path.extname(resolved.abs));
|
|
274
|
+
try {
|
|
275
|
+
const u = new URL(resolved.url);
|
|
276
|
+
const last = u.pathname.split('/').filter(Boolean).pop() || 'page';
|
|
277
|
+
return path.basename(last, path.extname(last));
|
|
278
|
+
} catch {
|
|
279
|
+
return 'page';
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Скриншот с деградацией при слишком высокой странице:
|
|
285
|
+
* обычный fullPage -> fullPage со scale:'css' -> только viewport.
|
|
286
|
+
*/
|
|
287
|
+
async function takeScreenshot(page, shot, { selector, fullPage }) {
|
|
288
|
+
const base = { path: shot.path, animations: 'disabled', caret: 'hide', type: 'png' };
|
|
289
|
+
if (selector) {
|
|
290
|
+
const locator = page.locator(selector).first();
|
|
291
|
+
const count = await page.locator(selector).count();
|
|
292
|
+
if (!count) throw new UsageError(`Селектор не найден: ${selector}`);
|
|
293
|
+
await locator.scrollIntoViewIfNeeded().catch(() => {});
|
|
294
|
+
await page.waitForTimeout(120);
|
|
295
|
+
await locator.screenshot({ ...base, scale: 'device' });
|
|
296
|
+
await measurePng(shot);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
await page.screenshot({ ...base, fullPage, scale: 'device' });
|
|
301
|
+
} catch (err) {
|
|
302
|
+
// Очень длинная страница при dpr>1 может не поместиться в лимит CDP.
|
|
303
|
+
try {
|
|
304
|
+
await page.screenshot({ ...base, fullPage, scale: 'css' });
|
|
305
|
+
shot.note = 'scale=css (страница слишком высокая для device-масштаба)';
|
|
306
|
+
} catch (err2) {
|
|
307
|
+
try {
|
|
308
|
+
await page.screenshot({ ...base, fullPage: false, scale: 'device' });
|
|
309
|
+
shot.note = `только видимая часть: ${err?.message || err2?.message || 'fullPage не удался'}`;
|
|
310
|
+
} catch {
|
|
311
|
+
throw err2;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
await measurePng(shot);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Размер PNG из заголовка IHDR — без зависимостей. */
|
|
319
|
+
async function measurePng(shot) {
|
|
320
|
+
const buf = fs.readFileSync(shot.path);
|
|
321
|
+
if (buf.length >= 24 && buf.toString('ascii', 12, 16) === 'IHDR') {
|
|
322
|
+
shot.widthPx = buf.readUInt32BE(16);
|
|
323
|
+
shot.heightPx = buf.readUInt32BE(20);
|
|
324
|
+
}
|
|
325
|
+
shot.bytes = buf.length;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
main().catch((err) => {
|
|
329
|
+
if (err?.isUsageError) {
|
|
330
|
+
console.error(`[capture] ${err.message}`);
|
|
331
|
+
console.error('[capture] подсказка: запустите node tools/design-qa/capture.mjs --help');
|
|
332
|
+
process.exitCode = 1;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
console.error('[capture] НЕОЖИДАННАЯ ОШИБКА:', err);
|
|
336
|
+
process.exitCode = 1;
|
|
337
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "design-qa",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Инструменты визуального QA для WOW-дизайна DSH: скриншоты через CDP, axe-аудит, валидатор портативного пакета",
|
|
5
|
+
"private": true,
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"capture": "node capture.mjs",
|
|
9
|
+
"audit": "node audit.mjs",
|
|
10
|
+
"validate": "node validate-package.mjs",
|
|
11
|
+
"fixtures": "node fixtures/make-fixtures.mjs",
|
|
12
|
+
"test:capture": "node capture.mjs fixtures/test.html --name fixture --out ../../tmp/qa-artifacts",
|
|
13
|
+
"test:audit": "node audit.mjs fixtures/test.html --name fixture-test --out ../../tmp/qa-artifacts",
|
|
14
|
+
"test:audit-broken": "node audit.mjs fixtures/broken.html --name fixture-broken --out ../../tmp/qa-artifacts",
|
|
15
|
+
"test:validate-ok": "node validate-package.mjs fixtures/package-ok",
|
|
16
|
+
"test:validate-bad": "node validate-package.mjs fixtures/package-bad"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"dsh",
|
|
20
|
+
"design-qa",
|
|
21
|
+
"screenshots",
|
|
22
|
+
"playwright",
|
|
23
|
+
"axe-core",
|
|
24
|
+
"accessibility"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"axe-core": "^4.14.0",
|
|
28
|
+
"playwright-core": "^1.64.1"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# 03 · Антипаттерны «ИИ-дизайна» — 42 штуки
|
|
2
|
+
|
|
3
|
+
> Часть навыка wow-design. ≈9k токенов. Читать, когда: проверяешь интерфейс на «AI-slop» или ищешь
|
|
4
|
+
> замену запрещённому приёму.
|
|
5
|
+
|
|
6
|
+
> Это не «невкусные приёмы», а маркеры, по которым интерфейс за 2 секунды опознаётся как
|
|
7
|
+
> сгенерированный. Задача документа — не просто запретить, а **чем заменить** (с конкретными
|
|
8
|
+
> значениями/токенами AURORA). Формат: как выглядит → почему плохо → чем заменить.
|
|
9
|
+
>
|
|
10
|
+
> Базовое правило: **любой приём допустим, если он осознан и дозирован.** Проблема не в градиенте
|
|
11
|
+
> как таковом, а в градиенте «по умолчанию» на каждом блоке. И не уходи в обратную крайность —
|
|
12
|
+
> стерильный минимализм без иерархии тоже выглядит дёшево (см. финальный раздел).
|
|
13
|
+
|
|
14
|
+
## Цвет, поверхности, глубина
|
|
15
|
+
|
|
16
|
+
### A01. Фиолетово-синие градиенты
|
|
17
|
+
**Как выглядит:** hero и кнопки с переходом `#667eea → #764ba2`, «индиго-вайблет» везде: кнопки, бейджи, фоны карточек, иконки.
|
|
18
|
+
**Почему плохо:** это палитра дефолтных AI-генераторов и шаблонов 2019–2022; она не связана ни с брендом, ни с задачей, и мгновенно читается как «сделал робот».
|
|
19
|
+
**Чем заменить:** один акцент из пресета (`--accent` #0E6E5E для aurora, #C8442A для editorial) + нейтрали; градиент — только как едва заметный tonal-переход фона (≤4% разницы), не на интерактивных элементах.
|
|
20
|
+
|
|
21
|
+
### A02. Радужные mesh-градиенты и «блобы»
|
|
22
|
+
**Как выглядит:** размытые цветные пятна за hero, aurora-mesh из 4–6 оттенков, светящиеся круги.
|
|
23
|
+
**Почему плохо:** отвлекают от контента, создают «шум» и пестрят; при этом не несут информации и плохо работают в тёмной теме.
|
|
24
|
+
**Чем заменить:** бумажный фон с лёгким зерном (`--grain-opacity:.022`), тонкая сетка (`--grid-line`), SVG-диаграмма или монограмма как визуальный якорь.
|
|
25
|
+
|
|
26
|
+
### A03. Glassmorphism на каждом блоке
|
|
27
|
+
**Как выглядит:** полупрозрачные панели с `backdrop-filter: blur(20px)`, белые обводки 1px, «стекло» на карточках, навигации, модалках.
|
|
28
|
+
**Почему плохо:** снижает контраст текста, жрёт производительность, выглядит как шаблон Apple-клона; стекло уместно 1 раз и только для overlay (sticky-хедер, модалка).
|
|
29
|
+
**Чем заменить:** матовые `--surface`/`--surface-2` + hairline; для sticky-хедера допустим полупрозрачный фон `color-mix(in oklab, var(--bg) 88%, transparent)` без blur.
|
|
30
|
+
|
|
31
|
+
### A04. Градиентный текст
|
|
32
|
+
**Как выглядит:** заголовки с `background-clip:text` и фиолетово-розовым градиентом, «переливающиеся» цифры.
|
|
33
|
+
**Почему плохо:** контраст зависит от позиции буквы на градиенте, часть текста проваливается ниже 4.5:1; выглядит как маркетинговый баннер, а не UI.
|
|
34
|
+
**Чем заменить:** однотонный `--ink`; акцент — на одном слове/детали через `--accent-ink`, но не градиентом.
|
|
35
|
+
|
|
36
|
+
### A05. Низкий контраст «ради эстетики»
|
|
37
|
+
**Как выглядит:** серый текст `#999` на белом, подписи `#B0B0B0`, «воздушные» лейблы, текст на полупрозрачных панелях.
|
|
38
|
+
**Почему плохо:** нарушает WCAG (нужно 4.5:1), нечитаемо на дешёвых экранах и при ярком свете; в AURORA `--faint` на `--bg` даёт 2.35:1.
|
|
39
|
+
**Чем заменить:** body — `--ink`/`--ink-2` (9–16:1); второстепенное — `--muted` только на `--surface` (4.8:1) или крупным кеглем; `--faint` — только декор.
|
|
40
|
+
|
|
41
|
+
### A06. Несогласованные отступы
|
|
42
|
+
**Как выглядит:** 13/17/22/27/31px, «на глаз»; одинаковые отступы внутри и между группами; блоки «прилипли» к краям.
|
|
43
|
+
**Почему плохо:** мозг считывает нерегулярность как неаккуратность; страница выглядит собранной на скорую руку.
|
|
44
|
+
**Чем заменить:** только `--sp-*` (4/8/12/16/24/32/48/64/96/128); внутри группы 8–16, между группами 32–64; проверить все `margin/padding/gap` через поиск магических чисел.
|
|
45
|
+
|
|
46
|
+
### A07. Тень на всём и одинаковые тени
|
|
47
|
+
**Как выглядит:** у каждой карточки, кнопки, бейджа и секции одна и та же `box-shadow`; тени не означают уровень.
|
|
48
|
+
**Почему плохо:** пропадает глубина: если всё приподнято, значит, ничего; поверхность выглядит «пластиковой».
|
|
49
|
+
**Чем заменить:** статичные карточки — hairline-бордер без тени; тень — только dropdown/modal/sticky, по шкале `--shadow-sm/md/lg` (2 уровня максимум).
|
|
50
|
+
|
|
51
|
+
### A08. Тени «как в 2010»
|
|
52
|
+
**Как выглядит:** `box-shadow: 0 0 30px rgba(0,0,0,.5)`, чёрные размытые пятна, тень со всех сторон.
|
|
53
|
+
**Почему плохо:** грязь под элементами, «дешёвый глянец»; в тёмной теме такие тени выглядят как сажа.
|
|
54
|
+
**Чем заменить:** слоистые тени из токенов: малый offset + большой blur с низкой alpha (`--shadow-md`, light: rgb(20 24 26 / .28)); в dark — усилить alpha, не blur.
|
|
55
|
+
|
|
56
|
+
### A09. Гигантские радиусы у всего
|
|
57
|
+
**Как выглядит:** cards/inputs/buttons — 24–32px, «пузыри»; всё скруглено одинаково сильно.
|
|
58
|
+
**Почему плохо:** выглядит как мобильное приложение для детей; теряются различия между контролом и контейнером; ужасно смотрится на плотных таблицах.
|
|
59
|
+
**Чем заменить:** `--radius-control` 8–12, `--radius-card` 12–18, радиус по смыслу; editorial/swiss-пресеты — 2–8px.
|
|
60
|
+
|
|
61
|
+
### A10. Pill-кнопки на всё
|
|
62
|
+
**Как выглядит:** все кнопки, бейджи, теги, табы — «таблетки» с радиусом 999px.
|
|
63
|
+
**Почему плохо:** pill — это семантика тега/чипа; когда всё pill, пропадает иерархия и появляется «маркетплейс-вид».
|
|
64
|
+
**Чем заменить:** pill только для статусов/чипов/аватаров; кнопки — `--radius-control`; secondary — ghost/контур.
|
|
65
|
+
|
|
66
|
+
### A11. Палитра из 6+ цветов, нет 60-30-10
|
|
67
|
+
**Как выглядит:** синий, фиолетовый, оранжевый, зелёный, розовый, бирюзовый — каждый «для акцента»; фоны разных цветов.
|
|
68
|
+
**Почему плохо:** глаз не понимает, что важно; бренд не запоминается; контрастные пары случайны и ломают AA.
|
|
69
|
+
**Чем заменить:** 60% нейтраль, 30% поверхности/текст, 10% акцент; редкий тёплый второй тон (`--copper`) ≤2% площади.
|
|
70
|
+
|
|
71
|
+
### A12. Акцент на всём
|
|
72
|
+
**Как выглядит:** акцентные заголовки, фоны секций, иконки, все кнопки, ссылки, рамки.
|
|
73
|
+
**Почему плохо:** акцент перестаёт быть акцентом; интерфейс выглядит «кричащим» и утомляет.
|
|
74
|
+
**Чем заменить:** акцент — только primary-CTA, активное состояние, ключевая цифра; заголовки — `--ink`; акцентного ≤10% площади экрана.
|
|
75
|
+
|
|
76
|
+
### A13. Тёмная тема как инверсия
|
|
77
|
+
**Как выглядит:** `#000` фон, `#fff` текст, тот же кислотный акцент, белые SVG с прозрачными подложками, исчезнувшие тени.
|
|
78
|
+
**Почему плохо:** максимальный контраст утомляет; тени и слои пропадают; изображения и логотипы «светятся» белыми прямоугольниками.
|
|
79
|
+
**Чем заменить:** отдельная палитра (в AURORA уже есть): фон #0B–#14, поверхности светлее с уровнем, акцент светлее (L +0.15–0.2), тени глубже; SVG — `currentColor`, при необходимости `@media`-вариант.
|
|
80
|
+
|
|
81
|
+
## Композиция и шаблонность
|
|
82
|
+
|
|
83
|
+
### A14. Три одинаковые карточки с иконками
|
|
84
|
+
**Как выглядит:** секция «Наши преимущества»: три равные карточки, три иконки в кружках, три заголовка в 3 слова, три абзаца по 2 строки.
|
|
85
|
+
**Почему плохо:** это визитная карточка AI-шаблона; нет иерархии, нет причины, почему именно три; карточки не несут разной ценности.
|
|
86
|
+
**Чем заменить:** редакционный нумерованный список (01/02/03) с hover-раскрытием; либо асимметричный сплит 7/5 с одной крупной мыслью и supporting-текстом; карточки — только для равнозначных данных (врачи, направления, новости).
|
|
87
|
+
|
|
88
|
+
### A15. Центрированный hero с блобами
|
|
89
|
+
**Как выглядит:** всё по центру: kicker, H1, подзаголовок, две кнопки, под ними — цветные пятна и парящая карточка.
|
|
90
|
+
**Почему плохо:** нулевая композиционная мысль, «шаблон за 5 минут»; на мобиле превращается в столбик.
|
|
91
|
+
**Чем заменить:** асимметричный hero 7/5 или 8/4: слева текст и CTA, справа — типографический якорь (крупные цифры, монограмма, SVG-композиция); либо редакционный «журнальный» первый экран с крупным H1 слева и метаданными справа.
|
|
92
|
+
|
|
93
|
+
### A16. Дефолтный Bootstrap/Shadcn-вид
|
|
94
|
+
**Как выглядит:** синие primary-кнопки 4px radius, стандартные инпуты с серой рамкой, та же тень у карточек, «карточка-заголовок-текст-кнопка».
|
|
95
|
+
**Почему плохо:** нет ни одного решения, которое говорит о заказчике; интерфейс выглядит как учебный проект.
|
|
96
|
+
**Чем заменить:** токены AURORA + одна signature-деталь; изменить хотя бы типографику (serif display), палитру (тёплая бумага/изумруд), форму контролов и редакционный ритм.
|
|
97
|
+
|
|
98
|
+
### A17. Иконки в цветных кружках
|
|
99
|
+
**Как выглядит:** каждая иконка в мягком кружке/скруглённом квадрате с пастельным фоном — в шапке, в карточках, в списке.
|
|
100
|
+
**Почему плохо:** визуальный шум, «детский» вид; иконки становятся декором, а не навигацией.
|
|
101
|
+
**Чем заменить:** голые stroke-иконки 20/24px в цвете `--muted`/`--ink-2`; цветной контейнер — только у статусов и точечно.
|
|
102
|
+
|
|
103
|
+
### A18. Всё по центру
|
|
104
|
+
**Как выглядит:** заголовки, лиды, карточки, секции — всё `text-align:center`, даже таблицы и списки.
|
|
105
|
+
**Почему плохо:** центр допустим для коротких блоков (модалка, 404), но длинный текст по центру читается плохо и ломает сетку.
|
|
106
|
+
**Чем заменить:** левое выравнивание для текста и сеток; центр — только для коротких пустых состояний/CTA-полос, осознанно.
|
|
107
|
+
|
|
108
|
+
### A19. Feature-grid «3×3 с галочками»
|
|
109
|
+
**Как выглядит:** стена из 6–9 одинаковых карточек с чекмарками и короткими пунктами «Быстро / Надёжно / Удобно».
|
|
110
|
+
**Почему плохо:** количество не заменяет смысл; пользователь не читает 9 равнозначных пунктов.
|
|
111
|
+
**Чем заменить:** 2–3 доказуемых факта с цифрами; остальное — в таблицу сравнения или в текст; галочки — только в тарифах/таблицах, где сравнение реально.
|
|
112
|
+
|
|
113
|
+
### A20. Стоковые фото улыбающихся людей
|
|
114
|
+
**Как выглядит:** hero с командой в офисе, рукопожатие, врач с планшетом, «счастливый клиент»; фотоаватарки-заглушки.
|
|
115
|
+
**Почему плохо:** мгновенно читается как сток, снижает доверие; по брифу AURORA внешние фото запрещены.
|
|
116
|
+
**Чем заменить:** типографика, SVG-диаграммы, монограммы, геометрические композиции, бумажная текстура; фото — только настоящие, если появятся.
|
|
117
|
+
|
|
118
|
+
### A21. Corporate Memphis / «люди с длинными руками»
|
|
119
|
+
**Как выглядит:** векторные человечки с гипертрофированными конечностями и пастельными пятнами.
|
|
120
|
+
**Почему плохо:** родом из 2019–2021, ассоциируется с безликими корпорациями; не несёт смысла, стоит денег/времени.
|
|
121
|
+
**Чем заменить:** простые геометрические SVG-схемы (путь пациента, SLA-график), моно-номера секций, монограммы.
|
|
122
|
+
|
|
123
|
+
### A22. Фейковые доказательства
|
|
124
|
+
**Как выглядит:** «10 000+ клиентов», «99.9% довольны», логотипы компаний, которых нет; отзывы без имён/дат.
|
|
125
|
+
**Почему плохо:** разрушает доверие при первой проверке; для медицинского сайта особенно опасно.
|
|
126
|
+
**Чем заменить:** реальные проверяемые цифры (число направлений, врачей, лет работы — с источником), реальные лицензии/оборудование; если данных нет — не выдумывать блок вообще.
|
|
127
|
+
|
|
128
|
+
### A23. Автопрокручиваемая карусель
|
|
129
|
+
**Как выглядит:** hero-слайдер, который сам листается каждые 5 секунд; точки-индикаторы; «пауза» отсутствует.
|
|
130
|
+
**Почему плохо:** пользователь не успевает прочитать, управление раздражает, WCAG 2.2.2 требует остановки; на мобиле это просто плохо.
|
|
131
|
+
**Чем заменить:** статичный hero с одной мыслью; если контента много — сетка/табы с ручным управлением; движения — только по действию.
|
|
132
|
+
|
|
133
|
+
### A24. Анимации ради анимаций
|
|
134
|
+
**Как выглядит:** счётчики, которые «набегают» при каждом появлении; бегущие градиентные подложки; пульсирующие кнопки; каждый элемент влетает с поворотом.
|
|
135
|
+
**Почему плохо:** отвлекает, тормозит, утомляет; движение не сообщает ничего о состоянии.
|
|
136
|
+
**Чем заменить:** движение только на действие/появление (120–260ms, `--ease`); reveal один раз на секцию (translateY 12–16px, opacity); в `prefers-reduced-motion` — отключено.
|
|
137
|
+
|
|
138
|
+
### A25. Скролл-джек и параллакс-цирк
|
|
139
|
+
**Как выглядит:** страница «пиннится», секции едут с разной скоростью, горизонтальный скролл, принудительные snap-точки.
|
|
140
|
+
**Почему плохо:** ломает привычное поведение колеса/трекпада, вызывает укачивание, недоступно с клавиатуры.
|
|
141
|
+
**Чем заменить:** обычный вертикальный скролл; ритм — отступами секций; если очень нужен эффект — один лёгкий parallax 5–10px и только на десктопе.
|
|
142
|
+
|
|
143
|
+
### A26. Модалка на модалке
|
|
144
|
+
**Как выглядит:** подтверждение открывается поверх модалки, тост поверх тоста, cookie-баннер поверх всего.
|
|
145
|
+
**Почему плохо:** пользователь теряет контекст и не понимает, что закрывать; растёт риск ошибок.
|
|
146
|
+
**Чем заменить:** одна модалка за раз; подтверждение — inline/в той же модалке; cookie-баннер — тонкая полоса снизу, не блокирующая контент.
|
|
147
|
+
|
|
148
|
+
### A27. Cookie-баннер во весь экран
|
|
149
|
+
**Как выглядит:** полноэкранный оверлей с «Принять всё / Настроить», который нельзя закрыть без согласия.
|
|
150
|
+
**Почему плохо:** первое впечатление — «помойка баннеров»; перекрывает контент; часто нарушает UX-принципы.
|
|
151
|
+
**Чем заменить:** полоса/карточка внизу с одним основным и одним второстепенным действием; контент читается сразу; кнопка закрытия видима.
|
|
152
|
+
|
|
153
|
+
## Типографика и текст
|
|
154
|
+
|
|
155
|
+
### A28. «16px везде»
|
|
156
|
+
**Как выглядит:** заголовки 20–24px, body 16px, подписи 14–16px; всё серое, всё одинаковое.
|
|
157
|
+
**Почему плохо:** нет иерархии — страница читается как документ; WOW не появляется без контраста масштабов.
|
|
158
|
+
**Чем заменить:** display 56–96px (clamp), body 16–18, служебное 11–13; разница уровней ≥1.25×; 2–3 начертания.
|
|
159
|
+
|
|
160
|
+
### A29. Длина строки 90–120 символов
|
|
161
|
+
**Как выглядит:** текст растянут на весь контейнер 1200–1360px; на широком мониторе строки «бегут».
|
|
162
|
+
**Почему плохо:** глаз теряет место при возврате на новую строку; чтение замедляется, страница выглядит небрежной.
|
|
163
|
+
**Чем заменить:** `max-width:var(--measure)` (66ch), лиды ≤52ch, подписи ≤48ch; колонки 5–6 из 12, не 12.
|
|
164
|
+
|
|
165
|
+
### A30. Body 14px и «мелкий шрифт в договоре»
|
|
166
|
+
**Как выглядит:** 13–14px основной текст, 11px подписи, всё в плотных блоках.
|
|
167
|
+
**Почему плохо:** нечитаемо на мобильном и при плохом зрении; выглядит как позорная копия банковского сайта.
|
|
168
|
+
**Чем заменить:** body 16–18px; 14–15 — только таблицы/сайдбар; минимум 12px для служебного; на мобиле не уменьшать.
|
|
169
|
+
|
|
170
|
+
### A31. Широкий трекинг в body и капс в длинных заголовках
|
|
171
|
+
**Как выглядит:** `letter-spacing:.1em` у абзацев; длинные заголовки CAPS LOCK.
|
|
172
|
+
**Почему плохо:** на кириллице широкий трекинг «рассыпает» слова; капс длинной фразы читается по буквам и выглядит крикливо.
|
|
173
|
+
**Чем заменить:** body −0.006em; капс — только короткие лейблы (≤3 слов, 11–12px, +0.12–0.14em); заголовки — обычный регистр.
|
|
174
|
+
|
|
175
|
+
### A32. Lorem ipsum и «рыба»
|
|
176
|
+
**Как выглядит:** «Lorem ipsum dolor sit amet», «Заголовок раздела», повторы «Текст-заглушка».
|
|
177
|
+
**Почему плохо:** нельзя оценить дизайн без реального контента; русская длина слов другая — макет «поедет» на настоящем тексте.
|
|
178
|
+
**Чем заменить:** правдоподобный русский контент под домен (организация, Москва): конкретные услуги, имена, цифры, сроки.
|
|
179
|
+
|
|
180
|
+
### A33. «Инновационные решения» и канцелярит
|
|
181
|
+
**Как выглядит:** «Мы предлагаем комплексный подход», «команда профессионалов», «инновационные решения для вашего здоровья».
|
|
182
|
+
**Почему плохо:** ноль информации, ноль доверия, невозможно отличить один сайт от тысячи таких же.
|
|
183
|
+
**Чем заменить:** глагол + конкретика: «Запись к кардиологу за 2 минуты», «94% заявок закрыты в SLA», «11 направлений, 40 врачей»; заголовок = результат.
|
|
184
|
+
|
|
185
|
+
### A34. Эмодзи вместо иконок
|
|
186
|
+
**Как выглядит:** 🚀 в кнопке, ✅ в списке, 💎 в заголовке; эмодзи как bullets и иконки.
|
|
187
|
+
**Почему плохо:** разный рендер на ОС, «несерьёзный» вид, нет единства stroke-толщин; на скриншотах в другом браузере — другая картинка.
|
|
188
|
+
**Чем заменить:** inline SVG, stroke 1.5–1.6, 20/24px, единый набор; цвет — `currentColor`.
|
|
189
|
+
|
|
190
|
+
### A35. Иконочный разнобой и «rocket»
|
|
191
|
+
**Как выглядит:** облачка, ракеты, лампочки, шестерёнки; иконки разной толщины, из разных наборов, с заливкой и без; «ракета» у CTA.
|
|
192
|
+
**Почему плохо:** rocket/cloud — клише стартап-лендингов; разнобой выдаёт сборку из готовых блоков.
|
|
193
|
+
**Чем заменить:** один stroke-набор; иконка только там, где помогает смыслу (навигация, статусы), не у каждого заголовка; вместо rocket — конкретный глагол в кнопке.
|
|
194
|
+
|
|
195
|
+
### A36. Бессмысленные бейджи и статусы
|
|
196
|
+
**Как выглядит:** NEW/HOT/TOP/«Рекомендуем» на каждом втором элементе; бейджи без действия; статус только цветной точкой.
|
|
197
|
+
**Почему плохо:** шум, конкуренция за внимание, цветовая слепота; статус должен читаться.
|
|
198
|
+
**Чем заменить:** бейдж только для реально нового/важного, ≤2 на экран; статус = точка + текст (+ иконка при необходимости); цвета — из семантических токенов.
|
|
199
|
+
|
|
200
|
+
## Компоненты и состояния
|
|
201
|
+
|
|
202
|
+
### A37. Только default и hover
|
|
203
|
+
**Как выглядит:** кнопка меняет фон при наведении — и всё; нет focus, active, disabled, loading, error.
|
|
204
|
+
**Почему плохо:** клавиатура «слепая», после нажатия непонятно, сработало ли, формы не объясняют ошибок.
|
|
205
|
+
**Чем заменить:** 7 состояний у интерактива; focus-visible через `--focus-ring`; loading — spinner/skeleton; error — под полем + `aria-invalid`.
|
|
206
|
+
|
|
207
|
+
### A38. Disabled, похожий на активный
|
|
208
|
+
**Как выглядит:** кнопка с обычной заливкой, но `disabled`; или прозрачность 0.9 и `cursor:default`.
|
|
209
|
+
**Почему плохо:** пользователь жмёт и не понимает, почему ничего не происходит.
|
|
210
|
+
**Чем заменить:** opacity 45–55%, `cursor:not-allowed`, без hover; рядом — объяснение «Сначала выберите врача».
|
|
211
|
+
|
|
212
|
+
### A39. «Что-то пошло не так»
|
|
213
|
+
**Как выглядит:** generic-ошибка в тосте без поля, кода и следующего шага; «Error 500»; «Некорректный ввод».
|
|
214
|
+
**Почему плохо:** человек не знает, что чинить; для медицинских форм — потеря данных и доверия.
|
|
215
|
+
**Чем заменить:** что случилось + что сделать + где: «Не удалось сохранить: нет соединения. Проверьте сеть и повторите»; ошибка у поля, данные сохранены.
|
|
216
|
+
|
|
217
|
+
### A40. Placeholder вместо лейбла
|
|
218
|
+
**Как выглядит:** поле без `<label>`, подпись только внутри как placeholder; при вводе подпись исчезает.
|
|
219
|
+
**Почему плохо:** пользователь забывает, что вводит; скринридер не читает подпись; ошибки непонятны.
|
|
220
|
+
**Чем заменить:** видимый `<label>` над полем; placeholder — только пример («+7 900 000-00-00»); `aria-describedby` на подсказки.
|
|
221
|
+
|
|
222
|
+
### A41. Таблица, ломающая мобильный
|
|
223
|
+
**Как выглядит:** широкая таблица растягивает `body`, на 390px появляется горизонтальный скролл всей страницы или обрезаются колонки.
|
|
224
|
+
**Почему плохо:** невозможно пользоваться на телефоне; считается критичным дефектом.
|
|
225
|
+
**Чем заменить:** `.table-wrap{overflow-x:auto}` + sticky первая колонка; key-колонки видимы, второстепенные — скрыть на мобиле; проверить `scrollWidth > innerWidth`.
|
|
226
|
+
|
|
227
|
+
### A42. Нет focus-visible, div-кнопки, hover-only
|
|
228
|
+
**Как выглядит:** `outline:none`, `<div onclick>`, действия только по наведению (иконки в строке таблицы).
|
|
229
|
+
**Почему плохо:** интерфейс недоступен с клавиатуры и на тач-устройствах; axe даёт serious/critical.
|
|
230
|
+
**Чем заменить:** `<button>`/`<a>`, `:focus-visible` кольцо, действия-иконки всегда видимы на мобиле и по фокусу на десктопе.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Быстрая проверка «AI-slop» за 5 секунд
|
|
235
|
+
|
|
236
|
+
1. Есть фиолетово-синий градиент / цветные блобы? → **сразу нет**.
|
|
237
|
+
2. Три одинаковые карточки с иконками в первом экране? → **нет**.
|
|
238
|
+
3. Заголовок 20–24px при body 16px? → **нет иерархии**.
|
|
239
|
+
4. Серый текст ниже 4.5:1 / подписи `#999`? → **нечитаемо**.
|
|
240
|
+
5. Отступы 13/17/22px? → **несогласованно**.
|
|
241
|
+
6. Эмодзи/иконки-ракеты/стекло/радиусы 24px у всего? → **шаблон**.
|
|
242
|
+
7. Lorem ipsum / «инновационные решения»? → **нет контента**.
|
|
243
|
+
8. Нет focus-visible / только hover? → **недоступно**.
|
|
244
|
+
|
|
245
|
+
Если хотя бы один пункт «да» — это P0-блокер, до рубрики 4.4 не допускается.
|
|
246
|
+
|
|
247
|
+
## Не переусердствуй: анти-антипаттерны
|
|
248
|
+
|
|
249
|
+
- **Стерильный минимализм:** белый экран, серый текст 16px, тонкие линии — «чисто», но нет иерархии и характера. Минимализм требует **сильной типографики и точного воздуха**, иначе это A28/A30.
|
|
250
|
+
- **Погоня за «дорого»:** всё serif и бежевое — без контраста, акцента и цифр получается «йога-студия», а не клиника.
|
|
251
|
+
- **Убрать всё:** карточки, иконки и цвет — не зло сами по себе. Зло — **дефолт без смысла**. Одна карточка с реальным содержанием лучше, чем ноль карточек.
|
|
252
|
+
- **Красота в ущерб функции:** контраст, размер кегля, состояния и доступность не приносятся в жертву композиции. Сначала ясность, потом WOW.
|