Audited the framework against WCAG 2.2 Level AA and applied the fixes
that block conformance. Sites using `theme: "base"` and the bundled
components inherit the improvements; sites with their own theme keep
their palette but pick up the structural/semantic fixes.
Palette (base theme):
- primary #4f6cf0 → #4361dd (4.43 → 4.91 on white)
- accent #f18f3b → #b45a00 (2.40 → 4.51 on white); original orange
preserved as palette.accentDecorative for fills only
- field.inputPlaceholder rgba(...64) → solid #54627b (2.82 → 5.66)
- new successText/warningText/criticalText for AA-safe status messaging
- primary CTA gradient lighter stop darkened to fix white-on-stop fail
New tokens (tokens.hero / tokens.footer / tokens.plan), exposed via
buildCssVarMap as --brand-hero-*, --brand-footer-*, --brand-plan-*. These
eliminate the fallback-chain trap that left FooterMinimal text invisible
on its own dark bg, the Hero headline at 1.4:1 on the dark hero surface,
and the Plan card title at 1.05:1 on its dark card.
Components:
- Home.vue + templates/index.html — skip link, single <main> landmark
(id="main-content" tabindex="-1"), noscript fallback for fade-up-in,
scroll-padding-top so sticky header doesn't obscure focused targets
- Header.vue — <nav aria-label="Primary">, descriptive logo aria-label,
locale dropdown ESC + focus-out dismiss with focus restoration; remove
the mismatched aria-haspopup="true"
- Hero.vue — promote headline to <h1>, demote eyebrow to <p>, point text
at the new hero token chain
- Contact.vue — real <label> per input, visible * required indicator,
aria-required, autocomplete hints, role="status" aria-live for #msg,
aria-describedby + aria-invalid wiring on the spam-check field
- ComingSoonModal.vue — capture/restore previousFocusedElement, 32×32
close-button hit area, AA-safe color, visible :focus-visible outline,
global ESC handler
- Footer.vue — visually-hidden "(opens in a new tab)" suffix; remove the
redundant aria-label="Return home" on logo
- FooterMinimal.vue, Plan.vue, Team.vue — read from new chrome tokens
- Portfolio.vue — per-card aria-label uses project.title; filters get
visible :focus-visible outline
- StickyCTA.vue — gradient mid-stop darkened so dark text passes
- base.css — :focus-visible on .ui-backtotop; placeholder fallback solid
Tests: 336 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>