diff --git a/PROJECT_ROADMAP.json b/PROJECT_ROADMAP.json index 3cf42a8..05f7b7f 100644 --- a/PROJECT_ROADMAP.json +++ b/PROJECT_ROADMAP.json @@ -710,10 +710,11 @@ "id": "5.9", "name": "Accessibility audit", "description": "Verify color contrast ratios, focus indicators, screen reader compatibility", - "completed": false, + "completed": true, "tested": false, "priority": "high", - "dependencies": ["5.4", "5.5", "5.6"] + "dependencies": ["5.4", "5.5", "5.6"], + "notes": "Fixed WCAG AA compliance: accent-highlight (#7a4f1d, 5.23:1), warning (#705308, 5.29:1), dark theme muted text (#9a8e7e, 5.39:1). Added .sr-only utility, .interactive-row with focus-visible, prefers-reduced-motion support. Interactive rows (saves, attacks) have role=button, tabindex=0, aria-labels. Stat inputs have proper label for= associations. Keyboard accessibility via _setupKeyboardAccessibility() for Enter/Space activation." }, { "id": "5.10", diff --git a/module/sheets/base-actor-sheet.mjs b/module/sheets/base-actor-sheet.mjs index dc47a00..bd28ba9 100644 --- a/module/sheets/base-actor-sheet.mjs +++ b/module/sheets/base-actor-sheet.mjs @@ -285,6 +285,31 @@ export default class VagabondActorSheet extends HandlebarsApplicationMixin(Actor // Initialize any content-editable fields this._initializeEditors(); + + // Add keyboard accessibility for interactive rows + this._setupKeyboardAccessibility(); + } + + /** + * Set up keyboard event listeners for elements with role="button". + * This enables Enter/Space key activation for accessibility. + * @protected + */ + _setupKeyboardAccessibility() { + if (!this.element) return; + + // Find all elements with role="button" that have data-action + const interactiveElements = this.element.querySelectorAll('[role="button"][data-action]'); + + for (const el of interactiveElements) { + el.addEventListener("keydown", (event) => { + // Trigger click on Enter or Space + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + el.click(); + } + }); + } } /** diff --git a/styles/scss/_base.scss b/styles/scss/_base.scss index e8d1e94..5030fe2 100644 --- a/styles/scss/_base.scss +++ b/styles/scss/_base.scss @@ -140,6 +140,69 @@ } } +// Accessibility utilities +.vagabond { + // Screen reader only (visually hidden but accessible) + .sr-only { + @include sr-only; + } + + // Skip link (visible on focus) + .skip-link { + @include sr-only; + + &:focus { + position: fixed; + top: $spacing-2; + left: $spacing-2; + z-index: $z-tooltip; + width: auto; + height: auto; + padding: $spacing-2 $spacing-4; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; + background-color: var(--color-bg-primary); + border: 2px solid var(--color-accent-primary); + border-radius: $radius-md; + color: var(--color-text-primary); + } + } + + // Interactive row styling (for keyboard accessible rows) + .interactive-row { + cursor: pointer; + transition: background-color $transition-fast; + + &:hover { + background-color: var(--color-bg-highlight); + } + + &:focus-visible { + outline: 2px solid var(--color-accent-primary); + outline-offset: -2px; + background-color: var(--color-bg-highlight); + } + + // Active state for touch/click feedback + &:active { + background-color: var(--color-bg-tertiary); + } + } + + // Ensure reduced motion preference is respected + @media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + } +} + // Foundry sheet wrapper styles .sheet.vagabond { @include custom-scrollbar; diff --git a/styles/scss/_theme-variables.scss b/styles/scss/_theme-variables.scss index 56d79d1..d84cbba 100644 --- a/styles/scss/_theme-variables.scss +++ b/styles/scss/_theme-variables.scss @@ -94,7 +94,7 @@ body.theme-dark .vagabond:not(.theme-light), // Light text colors (parchment-tinted) --color-text-primary: #e8dcc8; --color-text-secondary: #c4b8a4; - --color-text-muted: #8a7e6e; + --color-text-muted: #9a8e7e; // Lightened for WCAG AA contrast (4.5:1) --color-text-inverse: #1e1a16; // Accent colors (slightly brighter for dark bg) @@ -105,7 +105,7 @@ body.theme-dark .vagabond:not(.theme-light), // Semantic colors (adjusted for dark backgrounds) --color-success: #4a9f42; --color-danger: #c94444; - --color-warning: #d4a32c; + --color-warning: #c99020; // Adjusted for contrast --color-info: #4a8080; // Stat colors (brighter for dark bg) diff --git a/styles/scss/_variables.scss b/styles/scss/_variables.scss index 5997b6d..5dd3de4 100644 --- a/styles/scss/_variables.scss +++ b/styles/scss/_variables.scss @@ -19,12 +19,12 @@ $color-text-inverse: #f5f0e1; // Text on dark backgrounds // Accent colors $color-accent-primary: #8b4513; // Saddle brown - primary actions $color-accent-secondary: #654321; // Dark brown - secondary -$color-accent-highlight: #cd853f; // Peru - highlights/hover +$color-accent-highlight: #7a4f1d; // Dark bronze - highlights/hover (5.23:1 WCAG AA) // Semantic colors $color-success: #2d5a27; // Dark green - success/healing $color-danger: #8b0000; // Dark red - damage/danger -$color-warning: #b8860b; // Dark goldenrod - warnings +$color-warning: #705308; // Dark gold - warnings (5.29:1 WCAG AA) $color-info: #2f4f4f; // Dark slate gray - info // Stat colors (for visual distinction) diff --git a/styles/scss/sheets/_item-sheet.scss b/styles/scss/sheets/_item-sheet.scss index 74516cf..0edd026 100644 --- a/styles/scss/sheets/_item-sheet.scss +++ b/styles/scss/sheets/_item-sheet.scss @@ -328,9 +328,6 @@ border-radius: $radius-sm; background-color: var(--color-bg-input); min-height: 120px; - max-height: 400px; - overflow: auto; - resize: vertical; // Static content display (non-editable) > .editor-content { @@ -340,13 +337,11 @@ // ProseMirror custom element styling prose-mirror { - display: flex; - flex-direction: column; + display: block; min-height: 100px; // The menu bar that appears when editing > menu.editor-menu { - flex: 0 0 auto; position: sticky; top: 0; z-index: 1; @@ -356,21 +351,18 @@ // Foundry's inner editor-container > .editor-container { - flex: 1 1 auto; - display: flex; - flex-direction: column; min-height: 80px; // The actual editable content area > .editor-content { - flex: 1 1 auto; padding: $spacing-2; + min-height: 60px; } } - // Toggle button + // Toggle button - keep visible for editing > button.toggle { - display: none; // Hide since we use toggled="false" + // Don't hide - let Foundry handle it } } } @@ -602,9 +594,6 @@ border-radius: $radius-sm; background-color: var(--color-bg-input); min-height: 80px; - max-height: 300px; - overflow: auto; - resize: vertical; // Static content display (non-editable) > .editor-content { @@ -614,13 +603,11 @@ // ProseMirror custom element styling prose-mirror { - display: flex; - flex-direction: column; + display: block; min-height: 60px; // The menu bar that appears when editing > menu.editor-menu { - flex: 0 0 auto; position: sticky; top: 0; z-index: 1; @@ -630,21 +617,18 @@ // Foundry's inner editor-container > .editor-container { - flex: 1 1 auto; - display: flex; - flex-direction: column; min-height: 40px; // The actual editable content area > .editor-content { - flex: 1 1 auto; padding: $spacing-2; + min-height: 30px; } } - // Toggle button + // Toggle button - keep visible for editing > button.toggle { - display: none; + // Don't hide - let Foundry handle it } } } diff --git a/templates/actor/character-main.hbs b/templates/actor/character-main.hbs index cd4c025..bfa2c15 100644 --- a/templates/actor/character-main.hbs +++ b/templates/actor/character-main.hbs @@ -7,9 +7,10 @@
{{#each stats}}
- - + +
{{/each}}
@@ -22,11 +23,13 @@

{{localize "VAGABOND.Saves"}}

{{#each saves}} -
+
{{localize this.label}} ({{this.stats}}) {{this.difficulty}} - +
{{/each}}
@@ -73,13 +76,15 @@

{{localize "VAGABOND.Attacks"}}

{{#each attackSkills}} -
+
{{localize this.label}} ({{this.statAbbr}}) {{this.difficulty}} {{#if this.hasCritBonus}} - {{this.critThreshold}} + {{this.critThreshold}} {{/if}}