diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 28c36469..10dde3c0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,6 +4,9 @@ on: push: branches: [main] workflow_dispatch: + schedule: + # Daily at 02:00 UTC — ensures deadline-based layout changes are reflected without a code push + - cron: "0 2 * * *" permissions: contents: write diff --git a/ACCESSIBILITY.md b/ACCESSIBILITY.md index dc51689d..9dcc2749 100644 --- a/ACCESSIBILITY.md +++ b/ACCESSIBILITY.md @@ -1,14 +1,18 @@ -# Accessibility Statement +# Accessibility OffOn is a platform for open source enthusiasts. We want everyone to be able to read, browse, and contribute, regardless of disability, assistive technology, or device. This document explains what we support today, how we test, and how to tell us when we get it wrong. +--- + ## Our Commitment -- **Target:** [WCAG 2.2 Level AA](https://www.w3.org/TR/WCAG22/) across every page on [offon.dev](https://offon.dev). +- **Target:** [WCAG 2.2 Level AA](https://www.w3.org/TR/WCAG22/) across every page on [offon.dev](https://offon.dev). WCAG 2.2 AA is the floor, not the goal. Every component must be genuinely usable by keyboard-only users, screen reader users, and people with low vision. - **Both color modes:** light and dark mode must meet contrast and focus requirements. We do not ship a feature that only works in one mode. - **Keyboard first:** every interactive element is reachable and operable from the keyboard alone. - **No motion traps:** we honor `prefers-reduced-motion` and avoid auto-playing animation that the user did not request. +--- + ## What We Support Today - Skip-to-content link as the first focusable element on every page. @@ -22,6 +26,8 @@ OffOn is a platform for open source enthusiasts. We want everyone to be able to - Self-hosted fonts so users on restricted networks are not locked out. - Google Analytics is opt-in only via the consent banner. No tracking runs until the user accepts. +--- + ## Supported Environments - Modern evergreen browsers: Chrome, Edge, Firefox, Safari (current and previous major versions). @@ -29,6 +35,8 @@ OffOn is a platform for open source enthusiasts. We want everyone to be able to - Screen readers we test against during manual spot checks: VoiceOver on macOS and iOS, NVDA on Windows. - The site is fully static and served from GitHub Pages, so it works without JavaScript for reading content. Some interactive features (theme toggle, consent banner, filtering) require JavaScript. +--- + ## Known Limitations - We do not currently provide captions or transcripts because the site does not host video or audio. If we add media, captions and transcripts will ship with it. @@ -36,23 +44,29 @@ OffOn is a platform for open source enthusiasts. We want everyone to be able to If you find a barrier that is not listed here, please report it using the link below. We treat this list as evidence-based, not aspirational. +--- + ## How We Test ### Automated -- **axe-core via Playwright** on every pull request, configured in [`e2e/smoke.spec.ts`](e2e/smoke.spec.ts). Runs in both dark and light mode against the production build with tags `wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`, `wcag22aa`, and `best-practice`. The PR preview workflow blocks on these scans. +- **axe-core via Playwright** on every pull request, configured in [`e2e/smoke.spec.ts`](e2e/smoke.spec.ts). Runs in both dark and light mode against the production build with tags `wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`, `wcag22aa`, and `best-practice`. The PR preview workflow blocks on these scans. Never reduce this tag set. - **Vitest** assertions on landmark roles, labels, and focus behavior for components and hooks ([`src/test/`](src/test/)). +Automated axe passes are necessary but not sufficient. Automated tools catch roughly 30–40% of real-world accessibility issues. Manual testing is required for every interactive component. + ### Manual -For UI changes, contributors verify: +For every UI change, contributors must verify: -- Tab order matches the visual reading order. -- Focus is visible on every interactive element in both modes. -- The page works at 200% browser zoom and at 375px, 768px, and 1280px widths. -- Screen reader spot check on the changed flow (VoiceOver or NVDA). +1. **Keyboard-only navigation:** tab through the entire changed flow without a mouse. Tab order matches the visual reading order. No focus traps. Every interactive element is reachable and operable. +2. **Focus visibility:** focus ring is visible on every interactive element in both light and dark mode. +3. **Screen reader:** spot-check the changed flow with VoiceOver (macOS/iOS) or NVDA (Windows). Dynamic content updates are announced correctly. +4. **Zoom:** the page works at 200% browser zoom without content clipping or layout breakage. +5. **Viewports:** verify at 375px, 768px, and 1280px widths against the production build. +6. **Windows High Contrast Mode:** interactive states (hover, focus, disabled) are visible. Do not rely solely on background-color or semi-transparent borders to communicate state. -See the [Accessibility section in `CLAUDE.md`](CLAUDE.md#accessibility-wcag-22-aa-mandatory) for the full per-component checklist contributors apply. +--- ## Reporting an Accessibility Barrier @@ -63,7 +77,7 @@ If something on offon.dev blocks you or is hard to use, please tell us. We aim to acknowledge accessibility reports within five working days and to provide a workaround or fix timeline in the same response. -### Severity We Use +### Severity | Severity | Definition | |---|---| @@ -72,8 +86,133 @@ We aim to acknowledge accessibility reports within five working days and to prov | Medium | Inconsistent or annoying experience that does not block the task. | | Low | Minor issue with minimal impact on usability. | +--- + ## For Contributors -If you are submitting a pull request, every UI change should pass the checklist in our [pull request template](.github/pull_request_template.md). The full ruleset lives in [`CLAUDE.md`](CLAUDE.md#accessibility-wcag-22-aa-mandatory) and applies to every change. +Every UI change must pass the checklist below before the PR is submitted. See [`CLAUDE.md`](CLAUDE.md) for project conventions. + +--- + +## Contributor Checklist (Required for Every New Component) + +Apply this to every component you write or modify. + +### Color contrast + +- Normal text (under 18px / non-bold under 14px): minimum 4.5:1. +- Large text (18px+ or bold 14px+): minimum 3:1. +- UI components and focus indicators: minimum 3:1 against adjacent colors. +- Focus indicators (WCAG 2.4.11): the focus indicator area must be at least as large as a 2px perimeter outline of the component, and the focused/unfocused contrast ratio must be at least 3:1. +- Never use `hsl(41 100% 60%)` (`#ffc034` yellow) as text in light mode — fails contrast. +- Never place text on `bg-primary` without verifying light mode contrast. +- Never use `opacity-*` on an element that contains visible text. Use an explicit CSS color token instead (e.g. `text-[hsl(var(--text-faint))]`). +- Always verify contrast in both light and dark mode. +- Never rely on color alone to convey meaning. Always pair with text, icon, or pattern. + +### Focus rings + +- Pattern: `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-sm`. Inline elements: `ring-offset-1`. +- Always use `ring-ring`, never `ring-primary/xx`. +- Hover states must not change layout properties (padding, border, font-weight, width). Use color and opacity only. + +### Keyboard navigation + +- Every interactive element must be reachable and operable via keyboard. +- Tab order must follow a logical reading order. +- Never remove focus outlines. +- Modals must trap focus while open and return focus on close. Use shadcn `Dialog`. + +### Touch targets + +- Interactive elements must have a minimum touch target of 24x24px (WCAG 2.5.8). Prefer 44x44px for primary actions. +- Never rely on padding alone when the visible element is smaller than 24px. + +### Motion + +- All animations and transitions must respect `prefers-reduced-motion`. +- Wrap motion in `@media (prefers-reduced-motion: no-preference)` in CSS, or check `window.matchMedia('(prefers-reduced-motion: reduce)')` before triggering JS-driven animation. + +### Semantic HTML + +- Use the correct element for the job (`