Accessibility Audit Report

Date: 2026-05-05 Scope: thinking-routing-atlas-v2.html (single-page interactive a11y/AEO atlas) Standard: WCAG 2.2 AA Auditor: Claude Code (a11y-audit)
70
Overall
60
Color & Contrast
30
Keyboard Navigation
70
Screen Reader
100
Forms
55
Responsive & Reflow
88
Touch & Target
82
Cognitive
92
Motion
100
Media
55
Agent Operability & AEO
NEEDS WORK 12 issues found (4 critical, 5 warnings, 3 tips)
Overview
Findings
Legal Risk
Remediation

Category Summary

Category Pass Fail Review Coverage
Color & Contrast 6 3 0
60%
Keyboard Navigation 4 4 0
30%
Screen Reader 7 2 0
70%
Forms 0 0 0
100%
Responsive & Reflow 5 2 0
55%
Touch & Target 4 0 0
88%
Cognitive 5 0 0
82%
Motion 4 0 0
92%
Media 0 0 0
100%
Agent Operability & AEO 4 4 0
55%

Color & Contrast

--ink-faint (#8A8578) fails AA: 3.44:1 contrast on paper background 1.4.3 AA

Affected users: Low-vision users, elderly users, users in bright daylight

Location: :root --ink-faint var, used in .filter-label, .shortlist-info, .card-meta-row, .card-cta, .head-right, .anti-list, footnote text, ~30+ usages

axe-core flagged 66 nodes; ink-faint is the primary culprit. Used widely for small labels (10-11px). Ratio is 3.44:1 vs 4.5:1 AA minimum.

Fix: Darken --ink-faint from #8A8578 to #6B6655 (raises contrast to 5.4:1, still keeps muted feel).
Before / After
Before
--ink-faint: #8A8578;
After
--ink-faint: #6B6655;  /* 5.4:1 on --paper, passes AA */
--cat-1 (#B85C3D) fails AA when used as small text: 4.23:1 1.4.3 AA

Affected users: Low-vision users

Location: .cat-num for cat 1, .matrix-table .ref, .combo-load, .anti-list bullets

Need 4.5:1 for normal text. Currently 4.23. Borderline but fails.

Fix: Darken to #A04F33 (raises to ~4.8:1) or limit usage to ≥18pt large text where 3:1 is acceptable.
Before / After
Before
--cat-1: #B85C3D;
After
--cat-1: #A04F33;  /* 4.8:1 on --paper */
🔴 --cat-2 (#C19A3D) catastrophically fails AA: 2.47:1 on paper 1.4.3 AA

Affected users: Low-vision users

Location: .cat-num for cat-2 (Curriculum 課程型), border-color usages

Used as text color for category 2 number labels. 2.47:1 ratio. Light ochre on cream is one of the worst common combinations.

Fix: Darken cat-2 to #8A6E1F (≈4.7:1 on paper). Will shift the gold toward dark mustard but still distinct from other cats. OR keep for borders only (3:1 is acceptable for non-text UI components) and use a darker variant for text.
Before / After
Before
--cat-2: #C19A3D;
After
--cat-2: #8A6E1F;  /* darker ochre, 4.7:1 */
--cat-2-border: #C19A3D;  /* keep light for borders/accents */

Keyboard Navigation

🔴 23 paradigm cards are mouse-only (div with click handler, no keyboard support) 2.1.1 A

Affected users: Keyboard-only users, screen reader users, motor-impaired users using switch devices

Location: .card elements (23 nodes); buildCard() in script

Cards are

elements with click handlers but no role, tabindex, or key event listener. Tab cannot reach them, Enter/Space cannot activate. The primary atlas browsing interaction is unreachable without a mouse.

Fix: Make cards proper
Before / After
Before
card.addEventListener('click', () => openDetail(p.id));
// card has no tabindex, no role, no keydown
After
// Option A (preferred): use a <button>
const card = document.createElement('button');
card.type = 'button';
card.className = 'card';
// ... rest unchanged

// Option B: make div behave like button
card.tabIndex = 0;
card.setAttribute('role', 'button');
card.addEventListener('click', () => openDetail(p.id));
card.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openDetail(p.id); }
});
🔴 3 preset combo cards have click handler but no keyboard support 2.1.1 A

Affected users: Keyboard-only users

Location: .combo elements (3 nodes); renderPresets() in script

The recommended combo cards (一鍵載入) cannot be activated without a mouse. Same pattern as f1.

Fix: Same as f1 — convert to
Before / After
Before
div.addEventListener('click', () => loadCombo(preset.paradigms));
After
const div = document.createElement('button');
div.type = 'button';
div.className = 'combo';
div.addEventListener('click', () => loadCombo(preset.paradigms));
🔴 Synergy items in detail panel are clickable but not keyboard-reachable 2.1.1 A

Affected users: Keyboard-only users

Location: .synergy-item (li elements with click handler)

The synergy list inside detail panel lets you jump to another paradigm. Mouse-only.

Fix: Convert to
Before / After
Before
li.addEventListener('click', () => openDetail(s.with));
After
// Wrap inner content in a button:
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'synergy-link';
btn.append(head, why);
btn.addEventListener('click', () => openDetail(s.with));
li.appendChild(btn);
🔴 Workbench suggestion items have click handler but no keyboard support 2.1.1 A

Affected users: Keyboard-only users

Location: .wb-suggestion (div with click)

Same div-as-button anti-pattern. Suggestions are also draggable but drag is mouse-only too.

Fix: Convert to

Screen Reader

Heading hierarchy skips levels (h1 → h3 in workbench) 1.3.1 A

Affected users: Screen reader users who navigate by headings

Location: Workbench section h3 elements directly under document with h1 only

Workbench h3 (你的組合 / 適合用在 / 建議補上) appear after the page h1 with no h2 in between (until the categories). Heading navigation (rotor) becomes confusing.

Fix: Either add a wrapping section with an h2 (e.g., 'Your Combo Workbench') or change the workbench h3 elements to h2.
Before / After
Before
<section class="workbench"> ... <h3>你的組合</h3> ... </section>
After
<section class="workbench" aria-labelledby="wb-h"> <h2 id="wb-h" class="sr-only">Combo Workbench</h2> ... <h3>你的組合</h3> ... </section>
💡

Affected users: Screen reader users using landmark navigation

Location: 6 .cat-meta

💡 No skip link to main content; long page (23 cards) requires extensive Tab navigation 2.4.1 A

Affected users: Keyboard users, screen reader users

Location: top

After fixing keyboard support on cards, this becomes important — without a skip link, users tab through control-bar (7+ stops) before reaching main content.

Fix: Add a visually hidden skip link as first focusable element.

Forms

No findings in this category.

Responsive & Reflow

Page overflows horizontally at 320px viewport (WCAG 1.4.10 Reflow) 1.4.10 AA

Affected users: Mobile users, zoom users (zoom equivalent of 400% on 1280px desktop)

Location: main { max-width: 1640px; padding: 0 64px } and .cards { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)) }

scrollWidth=487px on innerWidth=320px → 167px horizontal overflow. The card grid's minmax(320px, 1fr) forces 320px minimum cards, plus padding pushes total beyond viewport.

Fix: Use minmax(min(320px, 100%), 1fr) so cards shrink below 320px when needed. Reduce padding to 16px on narrow viewports.
Before / After
Before
.cards { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
After
.cards { grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }
@media (max-width: 480px) {
  header.atlas-head, .control-bar, .workbench, main { padding-left: 16px; padding-right: 16px; }
}

Touch & Target

No findings in this category.

Cognitive

No findings in this category.

Motion

No findings in this category.

Media

No findings in this category.

Agent Operability & AEO

💡 No structured data (Schema.org JSON-LD), no Open Graph, no canonical URL best-practice (AEO) Best Practice

Affected users: AI agents, search engines, social media unfurl previews

Location:

If shared as a personal brand artifact, the lack of metadata means LLM crawlers / search engines can't index it well. No og:title, no canonical, no JSON-LD.

Fix: Add basic Open Graph tags + a JSON-LD WebSite/CreativeWork block.

Critical Findings

🔴 23 paradigm cards are mouse-only (div with click handler, no keyboard support) 2.1.1 A

Affected users: Keyboard-only users, screen reader users, motor-impaired users using switch devices

Location: .card elements (23 nodes); buildCard() in script

Cards are

elements with click handlers but no role, tabindex, or key event listener. Tab cannot reach them, Enter/Space cannot activate. The primary atlas browsing interaction is unreachable without a mouse.

Fix: Make cards proper
Before / After
Before
card.addEventListener('click', () => openDetail(p.id));
// card has no tabindex, no role, no keydown
After
// Option A (preferred): use a <button>
const card = document.createElement('button');
card.type = 'button';
card.className = 'card';
// ... rest unchanged

// Option B: make div behave like button
card.tabIndex = 0;
card.setAttribute('role', 'button');
card.addEventListener('click', () => openDetail(p.id));
card.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openDetail(p.id); }
});
🔴 3 preset combo cards have click handler but no keyboard support 2.1.1 A

Affected users: Keyboard-only users

Location: .combo elements (3 nodes); renderPresets() in script

The recommended combo cards (一鍵載入) cannot be activated without a mouse. Same pattern as f1.

Fix: Same as f1 — convert to
Before / After
Before
div.addEventListener('click', () => loadCombo(preset.paradigms));
After
const div = document.createElement('button');
div.type = 'button';
div.className = 'combo';
div.addEventListener('click', () => loadCombo(preset.paradigms));
🔴 Synergy items in detail panel are clickable but not keyboard-reachable 2.1.1 A

Affected users: Keyboard-only users

Location: .synergy-item (li elements with click handler)

The synergy list inside detail panel lets you jump to another paradigm. Mouse-only.

Fix: Convert to
Before / After
Before
li.addEventListener('click', () => openDetail(s.with));
After
// Wrap inner content in a button:
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'synergy-link';
btn.append(head, why);
btn.addEventListener('click', () => openDetail(s.with));
li.appendChild(btn);
🔴 Workbench suggestion items have click handler but no keyboard support 2.1.1 A

Affected users: Keyboard-only users

Location: .wb-suggestion (div with click)

Same div-as-button anti-pattern. Suggestions are also draggable but drag is mouse-only too.

Fix: Convert to
🔴 --cat-2 (#C19A3D) catastrophically fails AA: 2.47:1 on paper 1.4.3 AA

Affected users: Low-vision users

Location: .cat-num for cat-2 (Curriculum 課程型), border-color usages

Used as text color for category 2 number labels. 2.47:1 ratio. Light ochre on cream is one of the worst common combinations.

Fix: Darken cat-2 to #8A6E1F (≈4.7:1 on paper). Will shift the gold toward dark mustard but still distinct from other cats. OR keep for borders only (3:1 is acceptable for non-text UI components) and use a darker variant for text.
Before / After
Before
--cat-2: #C19A3D;
After
--cat-2: #8A6E1F;  /* darker ochre, 4.7:1 */
--cat-2-border: #C19A3D;  /* keep light for borders/accents */

Warnings

--ink-faint (#8A8578) fails AA: 3.44:1 contrast on paper background 1.4.3 AA

Affected users: Low-vision users, elderly users, users in bright daylight

Location: :root --ink-faint var, used in .filter-label, .shortlist-info, .card-meta-row, .card-cta, .head-right, .anti-list, footnote text, ~30+ usages

axe-core flagged 66 nodes; ink-faint is the primary culprit. Used widely for small labels (10-11px). Ratio is 3.44:1 vs 4.5:1 AA minimum.

Fix: Darken --ink-faint from #8A8578 to #6B6655 (raises contrast to 5.4:1, still keeps muted feel).
Before / After
Before
--ink-faint: #8A8578;
After
--ink-faint: #6B6655;  /* 5.4:1 on --paper, passes AA */
--cat-1 (#B85C3D) fails AA when used as small text: 4.23:1 1.4.3 AA

Affected users: Low-vision users

Location: .cat-num for cat 1, .matrix-table .ref, .combo-load, .anti-list bullets

Need 4.5:1 for normal text. Currently 4.23. Borderline but fails.

Fix: Darken to #A04F33 (raises to ~4.8:1) or limit usage to ≥18pt large text where 3:1 is acceptable.
Before / After
Before
--cat-1: #B85C3D;
After
--cat-1: #A04F33;  /* 4.8:1 on --paper */
Page overflows horizontally at 320px viewport (WCAG 1.4.10 Reflow) 1.4.10 AA

Affected users: Mobile users, zoom users (zoom equivalent of 400% on 1280px desktop)

Location: main { max-width: 1640px; padding: 0 64px } and .cards { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)) }

scrollWidth=487px on innerWidth=320px → 167px horizontal overflow. The card grid's minmax(320px, 1fr) forces 320px minimum cards, plus padding pushes total beyond viewport.

Fix: Use minmax(min(320px, 100%), 1fr) so cards shrink below 320px when needed. Reduce padding to 16px on narrow viewports.
Before / After
Before
.cards { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
After
.cards { grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }
@media (max-width: 480px) {
  header.atlas-head, .control-bar, .workbench, main { padding-left: 16px; padding-right: 16px; }
}
Heading hierarchy skips levels (h1 → h3 in workbench) 1.3.1 A

Affected users: Screen reader users who navigate by headings

Location: Workbench section h3 elements directly under document with h1 only

Workbench h3 (你的組合 / 適合用在 / 建議補上) appear after the page h1 with no h2 in between (until the categories). Heading navigation (rotor) becomes confusing.

Fix: Either add a wrapping section with an h2 (e.g., 'Your Combo Workbench') or change the workbench h3 elements to h2.
Before / After
Before
<section class="workbench"> ... <h3>你的組合</h3> ... </section>
After
<section class="workbench" aria-labelledby="wb-h"> <h2 id="wb-h" class="sr-only">Combo Workbench</h2> ... <h3>你的組合</h3> ... </section>

Tips & Best Practices

💡

Affected users: Screen reader users using landmark navigation

Location: 6 .cat-meta

💡 No skip link to main content; long page (23 cards) requires extensive Tab navigation 2.4.1 A

Affected users: Keyboard users, screen reader users

Location: top

After fixing keyboard support on cards, this becomes important — without a skip link, users tab through control-bar (7+ stops) before reaching main content.

Fix: Add a visually hidden skip link as first focusable element.
💡 No structured data (Schema.org JSON-LD), no Open Graph, no canonical URL best-practice (AEO) Best Practice

Affected users: AI agents, search engines, social media unfurl previews

Location:

If shared as a personal brand artifact, the lack of metadata means LLM crawlers / search engines can't index it well. No og:title, no canonical, no JSON-LD.

Fix: Add basic Open Graph tags + a JSON-LD WebSite/CreativeWork block.

Remediation Priority

P0 P0 -- Must Fix (Level A)

• Add keyboard support to cards / combos / synergies / suggestions (4 patterns) — 2.1.1 ~30 min
• Darken --ink-faint, --cat-1, --cat-2 to pass 4.5:1 AA — 1.4.3 ~10 min

P1 P1 -- Should Fix (Level AA)

• Fix 320px reflow with minmax(min(320px,100%),1fr) + tighter padding — 1.4.10 ~10 min
• Restructure heading hierarchy (h1 → h2 → h3, no skips) — 1.3.1 ~10 min

P2 P2 -- Nice to Fix (Best Practices)

• Replace aside with div for cat-meta blocks — best-practice ~5 min
• Add skip link — 2.4.1 ~5 min
• Add Open Graph + JSON-LD if planning to share publicly — AEO ~15 min

Testing Recommendations

  • After fixes, re-run axe-core to confirm 0 violations on color-contrast / region / landmark
  • Test keyboard-only flow: Tab through atlas, Enter to open detail, Tab in panel, Esc to close
  • Test with screen reader (NVDA on Windows / VoiceOver on Mac): browse by headings, by landmarks
  • Real-device test at 320px (iPhone SE) and 768px (iPad portrait)
  • If publishing: run beacon:inspect again to confirm score >= 85 for shareable artifact