frontend-perfection
Audit and polish a frontend — static HTML/CSS/JS or a built SPA — towards
verifiable perfection. The skill exists because production web perf tooling
commonly breaks when it pokes at private APIs (Playwright internals, old
Lighthouse signatures, unisolated global deps). This skill runs on STABLE
apis only, computes exactly (luminance, not eyeballs), and demands a
before/after report bound to audit ids.
When to use
- User asks to "make the layout perfect", "fix performance", "reach
100/100/100/100", "check the frontend", "audit the page".
- User wants an OG image or social share meta for a homepage.
- User reports contrast problems, missing SEO tags, broken anchors under a
fixed header, colors duplicated as raw hex.
Composition
- For Core Web Vitals / schema.org / SEO deep-dives — load
seo-toolkit (this skill
measures layout, contrast, tokens and a11y; the SEO-layer breadth lives there).
- For a distinctive visual DIRECTION before building — load
frontend-design-taste
(palette/type/signature), then run this skill to verify the result.
Workflow
- Locate the project — static files (index.html + css/) or built SPA.
- Serve it locally if needed:
python3 -m http.server 8377 (or the
project's dev server). Static files are fine over http://localhost.
- Run audit.js (Lighthouse, real Chrome):
node scripts/audit.js --url http://localhost:8377/ --mobile --out lh-mobile.json
node scripts/audit.js --url http://localhost:8377/ --desktop --out lh-desktop.json
Default threshold is 100; exit 0 only when every measured category is
at or above it. Add --only <category> to iterate on one category.
- Run meta_audit.py (SEO/contrast/headings/tokens, offline):
python3 scripts/meta_audit.py --html index.html --css main.css css/demo.css --out meta.json
Exit 0 = no violations; exit 1 = violations found.
- Run security_privacy_audit.py (Security/Privacy/i18n, offline) — see the
section below. Exit 0 = no violations; exit 1 = violations found.
- Fix by audit id — every fix must reference the audit it closes
(e.g.
audit.js color-contrast, meta_audit.py meta:description).
- Re-audit until green on both form factors. Only then call it done.
- Write the before/after report in the showcase format: input paths,
exact commands, real output, interpretation. Explicitly list what was
deliberately NOT done.
audit.js — Lighthouse runner (stable API)
meta_audit.py — static audit (Python stdlib, offline)
Checks and their ids:
| id |
what it verifies |
meta:title / meta:title-length |
title present, ≤ 60 chars |
meta:description / meta:description-length |
description present, ≤ 160 chars |
meta:canonical |
canonical link |
meta:og:*, meta:og:size |
OG tags; image must declare 1200×630 (crop-safe) |
meta:twitter:card |
twitter card tag |
meta:json-ld |
any application/ld+json script |
meta:robots |
not blocked with noindex/nofollow |
meta:sitemap-link |
sitemap referenced |
headings:single-h1 |
exactly one h1 |
headings:order |
h1→h6 sequence, no level skips (h1→h3 is a violation) |
tokens:raw-hex |
zero raw hex outside the token block (:root/tokens) |
contrast:wcag-aa |
computed WCAG relative luminance, fg/bg pairs ≥ 4.5:1 |
adaptive:scroll-padding |
fixed header ⇒ scroll-padding-top present |
adaptive:media-queries |
responsive breakpoints exist for tablet widths |
a11y:img-alt |
every <img> has an alt attribute (decorative may be empty) — WCAG 1.1.1 / axe image-alt |
a11y:button-name / a11y:link-name |
buttons and links have an accessible name (text or aria-label) — WCAG 4.1.2 / axe button-name, link-name |
a11y:form-label |
inputs/selects/textareas have a label (<label for>, wrapper, aria-label) — WCAG 4.1.2 / axe label |
a11y:aria-valid |
every aria-* attribute name is in the WAI-ARIA 1.2 set — axe aria-valid-attr |
a11y:landmark-unique |
at most one <main>; multiple <nav> need distinct labels — WCAG 1.3.1 / axe landmark-unique |
html:doctype |
HTML5 doctype on the first line |
html:charset |
charset declared as utf-8 |
html:viewport |
responsive viewport meta present |
html:lang |
<html lang> with a BCP 47 code (a11y + SEO) |
html:dir-rtl |
RTL languages (ar/he/fa/ur/yi) require dir="rtl" |
html:unique-id |
no duplicate id attributes |
html:semantic |
semantic elements used: header, main, footer |
html:favicons |
favicon link present |
html:web-app-manifest |
manifest.json referenced (PWA) |
html:sri |
CDN-hosted scripts carry integrity (SRI); local scripts exempt |
html:defer-async |
external scripts load with defer/async/type=module (no render-blocking) |
html:input-types |
inputs declare a type (not defaulting to text) |
images:dimensions |
img has width/height (CLS prevention) |
images:lazy-loading |
3+ images lazy-load offscreen content |
images:srcset |
responsive srcset used for fixed-size images |
images:modern-format |
raster jpg/png converted to webp/avif |
js:no-inline |
no inline handlers (onclick=…) or inline <script> blocks |
js:no-console |
no console.log/debug/warn in HTML |
css:focus-visible |
visible :focus-visible indicator for keyboard users |
css:print |
@media print stylesheet exists |
css:dark-mode |
prefers-color-scheme dark mode supported |
css:font-display |
webfonts use font-display: swap (no FOIT) |
perf:resource-hints |
preload/preconnect/dns-prefetch hints for critical origins (LCP) |
security:https |
no http:// URLs in src/href |
security:noopener |
target="_blank" links carry rel="noopener" |
privacy:consent |
cookie/consent mention present (GDPR / 152-ФЗ) |
nav:back-to-top |
long pages have a way back to top — logo links to top and/or a floating scroll-to-top button (bottom-right, aria-label, appears after scroll) — WCAG 2.4.1 / UX pattern |
Security, Privacy & i18n (static, offline)
scripts/security_privacy_audit.py extends the skill with the Front-End-Checklist
Security (22), Privacy (5) and Internationalization (5) categories — the
statically verifiable subset. It is pure Python 3 stdlib (no requests/bs4/
PyYAML), mirrors meta_audit.py's JSON/exit-code conventions, and emits audit ids
prefixed sec: / priv: / i18n:. It does NOT duplicate the dedicated sibling
skills' a11y/perf/testing checks — only Security/Privacy/i18n.
python3 scripts/security_privacy_audit.py --html index.html --css main.css --js app.js --out sec.json
python3 scripts/security_privacy_audit.py --html a.html b.html --json # stdout JSON
Exit 0 = no violations; 1 = ≥1 violation; 2 = runner error. The report is
machine-readable JSON only on stdout (no human summary) — evidence gate.
| id |
severity |
what it verifies |
sec:https |
high |
no http:// URLs in href/src (HTTPS everywhere) |
sec:mixed-content |
high |
no http:// in CSS url()/@import/srcset (mixed content) |
sec:csp |
medium |
Content-Security-Policy present (meta or server header) — OWASP A05 |
sec:sri |
high |
external <script>/<link> carry integrity (SRI) |
sec:security-headers |
low |
HSTS/XCTO/XFO/Referrer-Policy/Permissions-Policy (meta; server-side canonical) |
sec:noopener |
high |
target="_blank" carries rel="noopener noreferrer" |
sec:secrets-in-url |
critical |
no tokens/keys in query strings (token=/api_key=/secret=) |
sec:localstorage-secrets |
medium |
no secret keys in localStorage/sessionStorage |
sec:csrf |
medium |
state-changing (POST) forms carry a CSRF token |
sec:eval |
high |
no eval()/new Function() in JS |
sec:deprecated-crypto |
medium |
no md5/sha1 in security context |
sec:external-origins |
low |
external script origins enumerated (info) |
sec:internal-leak |
medium |
no internal IPs / .env references leaked |
sec:cookie-flags |
low |
document.cookie assignments use Secure |
priv:cookie-consent |
medium |
cookie/consent banner before non-essential tracking (GDPR / 152-ФЗ) |
priv:tracking-before-consent |
medium |
no tracking scripts (GA/GTM/fbq/Metrika) before consent |
priv:privacy-policy |
low |
privacy policy link present |
priv:dnt |
low |
Do Not Track / Global Privacy Control respected (info/manual) |
priv:third-party-data |
low |
no PII sent to third parties without consent (info) |
i18n:lang |
medium |
<html lang> present, valid BCP 47 |
i18n:dir |
medium |
RTL languages (ar/he/fa/ur/yi) set dir="rtl" |
i18n:intl-api |
low |
Intl.NumberFormat/DateTimeFormat used for formatting (info) |
i18n:charset-early |
medium |
<meta charset> within first 1024 bytes |
i18n:hardcoded-locale |
low |
no hardcoded locale-specific strings (info) |
Static subset only — runtime/header checks are manual
security_privacy_audit.py covers the static surface. The following require a
live server or manual review and are NOT asserted by the script:
- Real HTTP response headers (Strict-Transport-Security, X-Content-Type-Options,
X-Frame-Options, Referrer-Policy, Permissions-Policy, CSP as a header) — verify
with
frontend-performance's perf_headers.py (perf:sec:* / perf:hsts) or
curl -I.
- Live cookie flags (Secure/HttpOnly/SameSite on
Set-Cookie) — inspect response
headers.
- CSP violation reporting, real mixed-content in dev proxies, screen-reader/locale
correctness.
Routing to dedicated skills
For depth beyond this skill's baseline, delegate to the dedicated sibling skills
(same skills/ dir):
- Deep accessibility (95 rules: tables, landmarks, ARIA values, focus traps,
runtime contrast, screen-reader) →
frontend-a11y (scripts/a11y_audit.py
static + a11y_axe.mjs runtime).
- Deep performance (network/headers, Core Web Vitals, bundle size, HTTP/2,
caching, service worker) →
frontend-performance (perf_headers.py offline +
audit.js Lighthouse).
- E2E / visual / contract / a11y-in-CI testing (Playwright, jest-axe, Pact,
perf-budget CI) →
frontend-testing (scaffolds configs; does not re-audit).
Do NOT re-implement those domains here — this skill owns layout/perf-SEO/tokens +
the Security/Privacy/i18n static subset above; the siblings own their full
categories.
Design tokens — "colors as constants"
For static projects WITHOUT a build system (plain HTML/CSS/JS), a minimal
design system still applies:
- Every hex color must be declared once in the token block
(a
:root { --color-*: ...; } layer or a dedicated tokens.css).
- Zero raw hex anywhere else — components reference
var(--color-*).
meta_audit.py flags any raw hex outside the token block; this is the
enforceable version of "no hardcoded colors".
- The same idea extends to type scale and spacing (custom properties), but
the audit enforces colors only.
OG image generation — crop-safe social share
Social networks crop images differently (WhatsApp square-ish, Facebook
center-crop). The skill's rules, learned the hard way:
- Change the file name, never overwrite — social caches keyed by URL
will keep showing the old preview forever. New content ⇒ new URL
(e.g.
og-2026-08-09.png).
- Content must fit the central safe zone — standard 1200×630 canvas is
not enough: keep the hero/graphic content within ~600–640px centered
width so a square/center crop (≈630×630) never clips text or logos.
- Force a reflow before the screenshot — after setting width via inline
style, the first
screenshot() may capture the stale layout. Read
void element.offsetHeight (or getBoundingClientRect()) to force
layout sync, then shoot.
- Declare exact dimensions —
og:image:width/og:image:height =
1200/630 so scrapers don't guess and mis-trim.
- Re-test in 2+ networks after changing the URL (WhatsApp + Telegram at
minimum; they cache most aggressively).
Fixed-header anchoring
If position: fixed header exists, anchor jumps hide content underneath.
Fix: html { scroll-padding-top: <header-height> } (+ scroll-margin-top on
the anchors if needed). meta_audit.py reports adaptive:scroll-padding
when this is missing.
Constraints / non-goals
- audit.js opens a real Chrome; do not substitute CLI
lighthouse
headless-shell in the default call.
- Meta audit is offline and stdlib-only — no PyYAML, no requests.
- Do NOT fix with
!important spray or target 100 by hiding audits: raise
the underlying metrics (real fix at the root).
- Do NOT delete or modify user tests/screenshots; artifacts go to
--out
files you name.
- Lighthouse scores on
localhost vs production differ (no CDN, no real
TLS); state this in the report when it matters.
Examples
Full loop on a static site:
cd ~/projects/lovii_demo
python3 -m http.server 8377 &
node .../audit.js --url http://localhost:8377/ --mobile --out lh-mobile.json
node .../audit.js --url http://localhost:8377/ --desktop --out lh-desktop.json
python3 .../meta_audit.py --html index.html --css main.css css/demo.css --out meta.json
Iterate on one category until green:
node .../audit.js --url http://localhost:8377/ --mobile --only accessibility
See references/canonical-patterns.md for the canonical sources this skill
is aligned with, and the showcase in docs/showcase/showcase-frontend-perfection-lovii.md.
1---2name: frontend-perfection3description: Audit and polish frontend (static HTML/CSS/JS or built SPA) to measurable perfection: real-Chrome Lighthouse >=13 runs (mobile+desktop, no Playwright internals), SEO meta layer, WCAG contrast by computed luminance, heading order, a11y checks (axe-core subset), back-to-top navigation, design tokens (zero raw hex), adaptive checks, OG-image generation, plus Security/Privacy/i18n static coverage (HTTPS/mixed content, CSP, SRI, security headers, noopener, secrets-in-URL, cookie consent, i18n lang/dir/Intl). Front-End Checklist-inspired. Triggers: 'frontend audit', 'perfect the layout', 'lighthouse check', 'make it 100/100/100/100', 'audit the page', 'fix performance', 'contrast check', 'design tokens', 'og image', 'security headers', 'privacy', 'https', 'mixed content', 'CSP', 'SRI', 'i18n', 'internationalization', 'social share meta'.4license: MIT5---67# frontend-perfection89Audit and polish a frontend — static HTML/CSS/JS or a built SPA — towards10verifiable perfection. The skill exists because production web perf tooling11commonly breaks when it pokes at private APIs (Playwright internals, old12Lighthouse signatures, unisolated global deps). This skill runs on STABLE13apis only, computes exactly (luminance, not eyeballs), and demands a14before/after report bound to audit ids.1516## When to use1718- User asks to "make the layout perfect", "fix performance", "reach19 100/100/100/100", "check the frontend", "audit the page".20- User wants an OG image or social share meta for a homepage.21- User reports contrast problems, missing SEO tags, broken anchors under a22 fixed header, colors duplicated as raw hex.2324## Composition2526- For Core Web Vitals / schema.org / SEO deep-dives — load `seo-toolkit` (this skill27 measures layout, contrast, tokens and a11y; the SEO-layer breadth lives there).28- For a distinctive visual DIRECTION before building — load `frontend-design-taste`29 (palette/type/signature), then run this skill to verify the result.3031## Workflow32331. **Locate the project** — static files (index.html + css/) or built SPA.342. **Serve it locally** if needed: `python3 -m http.server 8377` (or the35 project's dev server). Static files are fine over http://localhost.363. **Run audit.js** (Lighthouse, real Chrome):37 ```bash38 node scripts/audit.js --url http://localhost:8377/ --mobile --out lh-mobile.json39 node scripts/audit.js --url http://localhost:8377/ --desktop --out lh-desktop.json40 ```41 Default threshold is 100; exit 0 only when every measured category is42 at or above it. Add `--only <category>` to iterate on one category.434. **Run meta_audit.py** (SEO/contrast/headings/tokens, offline):44 ```bash45 python3 scripts/meta_audit.py --html index.html --css main.css css/demo.css --out meta.json46 ```47 Exit 0 = no violations; exit 1 = violations found.48 - **Run security_privacy_audit.py** (Security/Privacy/i18n, offline) — see the49 section below. Exit 0 = no violations; exit 1 = violations found.505. **Fix by audit id** — every fix must reference the audit it closes51 (e.g. `audit.js` `color-contrast`, `meta_audit.py` `meta:description`).526. **Re-audit until green** on both form factors. Only then call it done.537. **Write the before/after report** in the showcase format: input paths,54 exact commands, real output, interpretation. Explicitly list what was55 deliberately NOT done.5657## audit.js — Lighthouse runner (stable API)5859- Real Chrome via **chrome-launcher** (default channel = installed Chrome;60 `--no-headless` to watch). No Playwright dependency, no private fields,61 no `_ws_url` transport hacks. CDP port comes from `chrome.port` (public).62- **Lighthouse >= 13 support**: module may export a namespace; the script63 falls back to `require("lighthouse").default` automatically.64- **Dependency isolation**: modules resolve local `node_modules` → `NODE_PATH`65 → `npm root -g`. The script *explains* how to install instead of dying with66 `Cannot find module`:67 ```bash68 npm i lighthouse chrome-launcher # in the script's dir69 # or globally:70 npm i -g lighthouse chrome-launcher && export NODE_PATH=$(npm root -g)71 ```72- Output: compact JSON (`--out`) with per-category scores and only the73 **failed weighted audits** (id, title, score, weight) — no 4000-line dump.74- Exit codes: `0` all categories ≥ threshold, `1` below threshold, `2` runner error.7576## meta_audit.py — static audit (Python stdlib, offline)7778Checks and their ids:7980| id | what it verifies |81|---|---|82| `meta:title` / `meta:title-length` | title present, ≤ 60 chars |83| `meta:description` / `meta:description-length` | description present, ≤ 160 chars |84| `meta:canonical` | canonical link |85| `meta:og:*`, `meta:og:size` | OG tags; image must declare 1200×630 (crop-safe) |86| `meta:twitter:card` | twitter card tag |87| `meta:json-ld` | any `application/ld+json` script |88| `meta:robots` | not blocked with noindex/nofollow |89| `meta:sitemap-link` | sitemap referenced |90| `headings:single-h1` | exactly one h1 |91| `headings:order` | h1→h6 sequence, no level skips (h1→h3 is a violation) |92| `tokens:raw-hex` | zero raw hex outside the token block (`:root`/tokens) |93| `contrast:wcag-aa` | computed WCAG relative luminance, fg/bg pairs ≥ 4.5:1 |94| `adaptive:scroll-padding` | fixed header ⇒ `scroll-padding-top` present |95| `adaptive:media-queries` | responsive breakpoints exist for tablet widths |96| `a11y:img-alt` | every `<img>` has an `alt` attribute (decorative may be empty) — WCAG 1.1.1 / axe `image-alt` |97| `a11y:button-name` / `a11y:link-name` | buttons and links have an accessible name (text or aria-label) — WCAG 4.1.2 / axe `button-name`, `link-name` |98| `a11y:form-label` | inputs/selects/textareas have a label (`<label for>`, wrapper, aria-label) — WCAG 4.1.2 / axe `label` |99| `a11y:aria-valid` | every `aria-*` attribute name is in the WAI-ARIA 1.2 set — axe `aria-valid-attr` |100| `a11y:landmark-unique` | at most one `<main>`; multiple `<nav>` need distinct labels — WCAG 1.3.1 / axe `landmark-unique` |101| `html:doctype` | HTML5 doctype on the first line |102| `html:charset` | charset declared as utf-8 |103| `html:viewport` | responsive viewport meta present |104| `html:lang` | `<html lang>` with a BCP 47 code (a11y + SEO) |105| `html:dir-rtl` | RTL languages (ar/he/fa/ur/yi) require `dir="rtl"` |106| `html:unique-id` | no duplicate `id` attributes |107| `html:semantic` | semantic elements used: header, main, footer |108| `html:favicons` | favicon link present |109| `html:web-app-manifest` | manifest.json referenced (PWA) |110| `html:sri` | CDN-hosted scripts carry `integrity` (SRI); local scripts exempt |111| `html:defer-async` | external scripts load with defer/async/type=module (no render-blocking) |112| `html:input-types` | inputs declare a `type` (not defaulting to text) |113| `images:dimensions` | img has width/height (CLS prevention) |114| `images:lazy-loading` | 3+ images lazy-load offscreen content |115| `images:srcset` | responsive srcset used for fixed-size images |116| `images:modern-format` | raster jpg/png converted to webp/avif |117| `js:no-inline` | no inline handlers (`onclick=…`) or inline `<script>` blocks |118| `js:no-console` | no `console.log/debug/warn` in HTML |119| `css:focus-visible` | visible `:focus-visible` indicator for keyboard users |120| `css:print` | `@media print` stylesheet exists |121| `css:dark-mode` | `prefers-color-scheme` dark mode supported |122| `css:font-display` | webfonts use `font-display: swap` (no FOIT) |123| `perf:resource-hints` | preload/preconnect/dns-prefetch hints for critical origins (LCP) |124| `security:https` | no `http://` URLs in src/href |125| `security:noopener` | `target="_blank"` links carry `rel="noopener"` |126| `privacy:consent` | cookie/consent mention present (GDPR / 152-ФЗ) |127| `nav:back-to-top` | long pages have a way back to top — logo links to top and/or a floating scroll-to-top button (bottom-right, aria-label, appears after scroll) — WCAG 2.4.1 / UX pattern |128129## Security, Privacy & i18n (static, offline)130131`scripts/security_privacy_audit.py` extends the skill with the Front-End-Checklist132**Security** (22), **Privacy** (5) and **Internationalization** (5) categories — the133statically verifiable subset. It is pure Python 3 stdlib (no `requests`/`bs4`/134`PyYAML`), mirrors `meta_audit.py`'s JSON/exit-code conventions, and emits audit ids135prefixed `sec:` / `priv:` / `i18n:`. It does NOT duplicate the dedicated sibling136skills' a11y/perf/testing checks — only Security/Privacy/i18n.137138```bash139python3 scripts/security_privacy_audit.py --html index.html --css main.css --js app.js --out sec.json140python3 scripts/security_privacy_audit.py --html a.html b.html --json # stdout JSON141```142143Exit `0` = no violations; `1` = ≥1 violation; `2` = runner error. The report is144**machine-readable JSON only** on stdout (no human summary) — evidence gate.145146| id | severity | what it verifies |147|---|---|---|148| `sec:https` | high | no `http://` URLs in href/src (HTTPS everywhere) |149| `sec:mixed-content` | high | no `http://` in CSS `url()`/`@import`/srcset (mixed content) |150| `sec:csp` | medium | Content-Security-Policy present (meta or server header) — OWASP A05 |151| `sec:sri` | high | external `<script>`/`<link>` carry `integrity` (SRI) |152| `sec:security-headers` | low | HSTS/XCTO/XFO/Referrer-Policy/Permissions-Policy (meta; server-side canonical) |153| `sec:noopener` | high | `target="_blank"` carries `rel="noopener noreferrer"` |154| `sec:secrets-in-url` | critical | no tokens/keys in query strings (`token=`/`api_key=`/`secret=`) |155| `sec:localstorage-secrets` | medium | no secret keys in localStorage/sessionStorage |156| `sec:csrf` | medium | state-changing (POST) forms carry a CSRF token |157| `sec:eval` | high | no `eval()`/`new Function()` in JS |158| `sec:deprecated-crypto` | medium | no md5/sha1 in security context |159| `sec:external-origins` | low | external script origins enumerated (info) |160| `sec:internal-leak` | medium | no internal IPs / `.env` references leaked |161| `sec:cookie-flags` | low | `document.cookie` assignments use `Secure` |162| `priv:cookie-consent` | medium | cookie/consent banner before non-essential tracking (GDPR / 152-ФЗ) |163| `priv:tracking-before-consent` | medium | no tracking scripts (GA/GTM/fbq/Metrika) before consent |164| `priv:privacy-policy` | low | privacy policy link present |165| `priv:dnt` | low | Do Not Track / Global Privacy Control respected (info/manual) |166| `priv:third-party-data` | low | no PII sent to third parties without consent (info) |167| `i18n:lang` | medium | `<html lang>` present, valid BCP 47 |168| `i18n:dir` | medium | RTL languages (ar/he/fa/ur/yi) set `dir="rtl"` |169| `i18n:intl-api` | low | `Intl.NumberFormat`/`DateTimeFormat` used for formatting (info) |170| `i18n:charset-early` | medium | `<meta charset>` within first 1024 bytes |171| `i18n:hardcoded-locale` | low | no hardcoded locale-specific strings (info) |172173### Static subset only — runtime/header checks are manual174175`security_privacy_audit.py` covers the **static** surface. The following require a176live server or manual review and are NOT asserted by the script:177- Real HTTP response headers (Strict-Transport-Security, X-Content-Type-Options,178 X-Frame-Options, Referrer-Policy, Permissions-Policy, CSP as a header) — verify179 with `frontend-performance`'s `perf_headers.py` (`perf:sec:*` / `perf:hsts`) or180 `curl -I`.181- Live cookie flags (Secure/HttpOnly/SameSite on `Set-Cookie`) — inspect response182 headers.183- CSP violation reporting, real mixed-content in dev proxies, screen-reader/locale184 correctness.185186### Routing to dedicated skills187188For depth beyond this skill's baseline, delegate to the dedicated sibling skills189(same `skills/` dir):190- **Deep accessibility** (95 rules: tables, landmarks, ARIA values, focus traps,191 runtime contrast, screen-reader) → `frontend-a11y` (`scripts/a11y_audit.py`192 static + `a11y_axe.mjs` runtime).193- **Deep performance** (network/headers, Core Web Vitals, bundle size, HTTP/2,194 caching, service worker) → `frontend-performance` (`perf_headers.py` offline +195 `audit.js` Lighthouse).196- **E2E / visual / contract / a11y-in-CI testing** (Playwright, jest-axe, Pact,197 perf-budget CI) → `frontend-testing` (scaffolds configs; does not re-audit).198199Do NOT re-implement those domains here — this skill owns layout/perf-SEO/tokens +200the Security/Privacy/i18n static subset above; the siblings own their full201categories.202203## Design tokens — "colors as constants"204205For static projects WITHOUT a build system (plain HTML/CSS/JS), a minimal206design system still applies:207208- Every hex color must be declared **once** in the token block209 (a `:root { --color-*: ...; }` layer or a dedicated `tokens.css`).210- **Zero raw hex** anywhere else — components reference `var(--color-*)`.211- `meta_audit.py` flags any raw hex outside the token block; this is the212 enforceable version of "no hardcoded colors".213- The same idea extends to type scale and spacing (custom properties), but214 the audit enforces colors only.215216## OG image generation — crop-safe social share217218Social networks crop images differently (WhatsApp square-ish, Facebook219center-crop). The skill's rules, learned the hard way:2202211. **Change the file name, never overwrite** — social caches keyed by URL222 will keep showing the old preview forever. New content ⇒ new URL223 (e.g. `og-2026-08-09.png`).2242. **Content must fit the central safe zone** — standard 1200×630 canvas is225 not enough: keep the hero/graphic content within ~600–640px centered226 width so a square/center crop (≈630×630) never clips text or logos.2273. **Force a reflow before the screenshot** — after setting width via inline228 style, the first `screenshot()` may capture the stale layout. Read229 `void element.offsetHeight` (or `getBoundingClientRect()`) to force230 layout sync, then shoot.2314. **Declare exact dimensions** — `og:image:width`/`og:image:height` =232 `1200`/`630` so scrapers don't guess and mis-trim.2335. Re-test in 2+ networks after changing the URL (WhatsApp + Telegram at234 minimum; they cache most aggressively).235236## Fixed-header anchoring237238If `position: fixed` header exists, anchor jumps hide content underneath.239Fix: `html { scroll-padding-top: <header-height> }` (+ `scroll-margin-top` on240the anchors if needed). `meta_audit.py` reports `adaptive:scroll-padding`241when this is missing.242243## Constraints / non-goals244245- audit.js opens a **real Chrome**; do not substitute CLI `lighthouse`246 headless-shell in the default call.247- Meta audit is offline and stdlib-only — no PyYAML, no requests.248- Do NOT fix with `!important` spray or target 100 by hiding audits: raise249 the underlying metrics (real fix at the root).250- Do NOT delete or modify user tests/screenshots; artifacts go to `--out`251 files you name.252- Lighthouse scores on `localhost` vs production differ (no CDN, no real253 TLS); state this in the report when it matters.254255## Examples256257**Full loop on a static site:**258```bash259cd ~/projects/lovii_demo260python3 -m http.server 8377 &261node .../audit.js --url http://localhost:8377/ --mobile --out lh-mobile.json262node .../audit.js --url http://localhost:8377/ --desktop --out lh-desktop.json263python3 .../meta_audit.py --html index.html --css main.css css/demo.css --out meta.json264```265266**Iterate on one category until green:**267```bash268node .../audit.js --url http://localhost:8377/ --mobile --only accessibility269```270271See `references/canonical-patterns.md` for the canonical sources this skill272is aligned with, and the showcase in `docs/showcase/showcase-frontend-perfection-lovii.md`.