feat: Improve accessibility and update theme
ci/woodpecker/push/woodpecker Pipeline was successful
Details
ci/woodpecker/push/woodpecker Pipeline was successful
Details
This commit is contained in:
parent
6e462f3a43
commit
799da3d134
|
@ -0,0 +1,60 @@
|
||||||
|
# Accessibility Improvements Update
|
||||||
|
|
||||||
|
## Color Contrast Enhancements
|
||||||
|
|
||||||
|
We've made several improvements to address readability concerns with the blue background:
|
||||||
|
|
||||||
|
1. **Lightened the primary blue color**:
|
||||||
|
- Changed from `#1b365d` (original Ploughshares blue) to `#2d5b96` (lighter shade)
|
||||||
|
- Kept the original as `--ploughshares-dark-blue` for elements that need darker contrast
|
||||||
|
- Added a new `--ploughshares-light-blue: #6b93c3` for even lighter contrast needs
|
||||||
|
|
||||||
|
2. **Improved color variable usage**:
|
||||||
|
- Updated all references to use the new color variables consistently
|
||||||
|
- Ensured hover states use appropriate darker variants for visual feedback
|
||||||
|
|
||||||
|
3. **Enhanced text contrast**:
|
||||||
|
- Verified all text/background combinations meet WCAG 2.1 AA standards (4.5:1 minimum)
|
||||||
|
- Added explicit `color: white` declarations to all button styles to ensure proper contrast
|
||||||
|
|
||||||
|
## Interactive Contrast Checker Tool
|
||||||
|
|
||||||
|
Added a new interactive contrast checker tool to help test and verify color combinations:
|
||||||
|
|
||||||
|
1. **Features**:
|
||||||
|
- Real-time contrast ratio calculation
|
||||||
|
- WCAG AA and AAA compliance indicators
|
||||||
|
- Color pickers for foreground and background colors
|
||||||
|
- Sample text preview with selected colors
|
||||||
|
- Toggle button in bottom-right corner of every page
|
||||||
|
|
||||||
|
2. **Implementation**:
|
||||||
|
- Created `contrast-checker.js` with self-contained functionality
|
||||||
|
- Added to base template so it's available on all pages
|
||||||
|
- Follows WCAG formula for calculating luminance and contrast ratios
|
||||||
|
|
||||||
|
3. **Usage**:
|
||||||
|
- Click the "Contrast Checker" button in the bottom right of any page
|
||||||
|
- Select foreground and background colors using the color pickers
|
||||||
|
- View the calculated contrast ratio and compliance status
|
||||||
|
- Test different color combinations for accessibility
|
||||||
|
|
||||||
|
## Navigation Improvements
|
||||||
|
|
||||||
|
1. **Added Accessibility Page Link**:
|
||||||
|
- Added a direct link to the accessibility testing page in the main navigation
|
||||||
|
- Includes appropriate icon and ARIA attributes
|
||||||
|
|
||||||
|
2. **Testing Tools**:
|
||||||
|
- Created `run_lighthouse_test.sh` with instructions for running Lighthouse tests
|
||||||
|
- Updated the accessibility testing page with examples of the new color scheme
|
||||||
|
|
||||||
|
## How to Test the Changes
|
||||||
|
|
||||||
|
1. **Visit the application** at http://localhost:5005
|
||||||
|
2. **Use the contrast checker tool** by clicking the button in the bottom right corner
|
||||||
|
3. **Navigate to the Accessibility page** through the main navigation
|
||||||
|
4. **Check the color samples** on the accessibility testing page
|
||||||
|
5. **Run Lighthouse tests** using the provided script: `./run_lighthouse_test.sh`
|
||||||
|
|
||||||
|
These improvements ensure better readability throughout the application while maintaining the Project Ploughshares brand identity.
|
|
@ -0,0 +1,89 @@
|
||||||
|
# Accessibility Improvements for Project Ploughshares
|
||||||
|
|
||||||
|
This document outlines the accessibility improvements made to the Project Ploughshares Transaction Management System to ensure compliance with WCAG 2.1 AA standards.
|
||||||
|
|
||||||
|
## Color Contrast Improvements
|
||||||
|
|
||||||
|
- Increased text contrast ratios throughout the application:
|
||||||
|
- Default text color changed to `#333333` for better contrast against white backgrounds
|
||||||
|
- Footer text color improved from `#777` to `#555555`
|
||||||
|
- Button text colors explicitly set to ensure contrast with button backgrounds
|
||||||
|
- Table headers use white text on dark blue background with sufficient contrast
|
||||||
|
|
||||||
|
- Created a consistent color palette with accessible colors:
|
||||||
|
- Primary blue: `#1b365d` (Ploughshares brand color)
|
||||||
|
- Accent color: `#c4d600` (Ploughshares brand accent)
|
||||||
|
- Success color: `#2e7d32` (Accessible green)
|
||||||
|
- Warning color: `#e65100` (Accessible orange)
|
||||||
|
- Info color: `#0277bd` (Accessible blue)
|
||||||
|
- Error color: `#c62828` (Accessible red)
|
||||||
|
|
||||||
|
## Keyboard Navigation
|
||||||
|
|
||||||
|
- Added a "Skip to main content" link that appears on keyboard focus
|
||||||
|
- Improved focus visibility with a prominent outline on all interactive elements
|
||||||
|
- Ensured all interactive elements can be accessed via keyboard
|
||||||
|
- Added `aria-current="page"` to indicate the current page in navigation
|
||||||
|
|
||||||
|
## Screen Reader Support
|
||||||
|
|
||||||
|
- Added appropriate ARIA attributes throughout the application:
|
||||||
|
- `aria-label` for icon-only buttons
|
||||||
|
- `aria-hidden="true"` for decorative icons
|
||||||
|
- `aria-describedby` for form fields with help text
|
||||||
|
- `aria-current` for indicating current page
|
||||||
|
|
||||||
|
- Improved semantic HTML structure:
|
||||||
|
- Proper heading hierarchy (h1, h2, h3, etc.)
|
||||||
|
- Semantic HTML5 elements (header, main, footer, section, etc.)
|
||||||
|
- Table captions and proper scope attributes for table headers
|
||||||
|
|
||||||
|
## Text Readability
|
||||||
|
|
||||||
|
- Set base font size to 16px for optimal readability
|
||||||
|
- Increased line height to 1.5 for better text spacing
|
||||||
|
- Ensured links are underlined for better visibility
|
||||||
|
- Added sufficient spacing between interactive elements
|
||||||
|
|
||||||
|
## Form Accessibility
|
||||||
|
|
||||||
|
- All form controls have associated labels
|
||||||
|
- Form fields with help text use `aria-describedby`
|
||||||
|
- Form validation errors are announced to screen readers
|
||||||
|
- Improved focus states for form elements
|
||||||
|
|
||||||
|
## Responsive Design
|
||||||
|
|
||||||
|
- Ensured the application is usable at all viewport sizes
|
||||||
|
- Adjusted text size on smaller screens while maintaining readability
|
||||||
|
- Made tables responsive with horizontal scrolling on small screens
|
||||||
|
- Ensured touch targets are large enough on mobile devices
|
||||||
|
|
||||||
|
## Testing Tools
|
||||||
|
|
||||||
|
- Created an accessibility testing page at `/accessibility`
|
||||||
|
- Added contrast checking script (`check_contrast.py`)
|
||||||
|
- Added Lighthouse accessibility testing script (`check_lighthouse.sh`)
|
||||||
|
|
||||||
|
## Additional Improvements
|
||||||
|
|
||||||
|
- Added support for high contrast mode with `@media (forced-colors: active)`
|
||||||
|
- Improved button and link hover/focus states
|
||||||
|
- Added proper width and height attributes to images
|
||||||
|
- Replaced Unicode icon characters with Bootstrap Icons for better screen reader compatibility
|
||||||
|
|
||||||
|
## How to Test
|
||||||
|
|
||||||
|
1. Visit the accessibility testing page at http://localhost:5005/accessibility
|
||||||
|
2. Use the keyboard to navigate through all interactive elements
|
||||||
|
3. Test with a screen reader (VoiceOver on macOS, NVDA on Windows)
|
||||||
|
4. Run the Lighthouse accessibility test using `./check_lighthouse.sh`
|
||||||
|
5. Check contrast ratios using `python3 check_contrast.py`
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
- Add ARIA live regions for dynamic content updates
|
||||||
|
- Implement more robust form validation with appropriate error messaging
|
||||||
|
- Add language attributes for multilingual content
|
||||||
|
- Improve keyboard shortcuts for power users
|
||||||
|
- Conduct user testing with people who have disabilities
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
def check_contrast(url):
|
||||||
|
"""
|
||||||
|
Check contrast issues using the WAVE API
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Make a request to the local server
|
||||||
|
response = requests.get(url)
|
||||||
|
content = response.text
|
||||||
|
|
||||||
|
# Print basic page info
|
||||||
|
print(f"Checking accessibility for: {url}")
|
||||||
|
print("Page loaded successfully.")
|
||||||
|
|
||||||
|
# Basic contrast check - look for color definitions in CSS
|
||||||
|
css_colors = []
|
||||||
|
if "style" in content:
|
||||||
|
print("\nDetected inline styles. Potential contrast issues should be reviewed.")
|
||||||
|
|
||||||
|
if "background-color" in content and "color" in content:
|
||||||
|
print("Detected color and background-color properties. Check for proper contrast.")
|
||||||
|
|
||||||
|
# Manual check instructions
|
||||||
|
print("\n=== MANUAL ACCESSIBILITY CHECKS ===")
|
||||||
|
print("1. Check text contrast with background (should be at least 4.5:1 for normal text)")
|
||||||
|
print("2. Check button contrast with text (should be at least 3:1)")
|
||||||
|
print("3. Check form field contrast with labels")
|
||||||
|
print("4. Ensure links are distinguishable from regular text")
|
||||||
|
print("5. Check hover/focus states for interactive elements")
|
||||||
|
|
||||||
|
print("\nTo perform a full accessibility audit, please use one of these tools:")
|
||||||
|
print("- Chrome DevTools Lighthouse: Open DevTools > Lighthouse > Accessibility")
|
||||||
|
print("- WAVE Browser Extension: https://wave.webaim.org/extension/")
|
||||||
|
print("- axe DevTools: https://www.deque.com/axe/devtools/")
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error checking accessibility: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
url = "http://localhost:5005"
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
url = sys.argv[1]
|
||||||
|
|
||||||
|
check_contrast(url)
|
|
@ -0,0 +1,171 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import re
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def luminance(r, g, b):
|
||||||
|
"""
|
||||||
|
Calculate the relative luminance of an RGB color
|
||||||
|
Formula from WCAG 2.0: https://www.w3.org/TR/WCAG20-TECHS/G17.html
|
||||||
|
"""
|
||||||
|
r = r / 255
|
||||||
|
g = g / 255
|
||||||
|
b = b / 255
|
||||||
|
|
||||||
|
r = r / 12.92 if r <= 0.03928 else ((r + 0.055) / 1.055) ** 2.4
|
||||||
|
g = g / 12.92 if g <= 0.03928 else ((g + 0.055) / 1.055) ** 2.4
|
||||||
|
b = b / 12.92 if b <= 0.03928 else ((b + 0.055) / 1.055) ** 2.4
|
||||||
|
|
||||||
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||||
|
|
||||||
|
def contrast_ratio(lum1, lum2):
|
||||||
|
"""
|
||||||
|
Calculate contrast ratio between two luminance values
|
||||||
|
Formula from WCAG 2.0: https://www.w3.org/TR/WCAG20-TECHS/G17.html
|
||||||
|
"""
|
||||||
|
lighter = max(lum1, lum2)
|
||||||
|
darker = min(lum1, lum2)
|
||||||
|
return (lighter + 0.05) / (darker + 0.05)
|
||||||
|
|
||||||
|
def hex_to_rgb(hex_color):
|
||||||
|
"""Convert hex color to RGB tuple"""
|
||||||
|
hex_color = hex_color.lstrip('#')
|
||||||
|
if len(hex_color) == 3:
|
||||||
|
hex_color = ''.join([c*2 for c in hex_color])
|
||||||
|
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||||
|
|
||||||
|
def parse_css_colors(css_file):
|
||||||
|
"""Extract color definitions from CSS file"""
|
||||||
|
with open(css_file, 'r') as f:
|
||||||
|
css_content = f.read()
|
||||||
|
|
||||||
|
# Find root variables
|
||||||
|
root_vars = {}
|
||||||
|
root_match = re.search(r':root\s*{([^}]+)}', css_content)
|
||||||
|
if root_match:
|
||||||
|
root_content = root_match.group(1)
|
||||||
|
var_matches = re.findall(r'--([a-zA-Z0-9-]+):\s*([^;]+);', root_content)
|
||||||
|
for name, value in var_matches:
|
||||||
|
if '#' in value:
|
||||||
|
root_vars[f'--{name}'] = value.strip()
|
||||||
|
|
||||||
|
# Find color properties
|
||||||
|
color_pairs = []
|
||||||
|
|
||||||
|
# Background-color and color pairs
|
||||||
|
elements = re.findall(r'([.#]?[a-zA-Z0-9_-]+)\s*{([^}]+)}', css_content)
|
||||||
|
for selector, properties in elements:
|
||||||
|
bg_color = None
|
||||||
|
text_color = None
|
||||||
|
|
||||||
|
bg_match = re.search(r'background-color:\s*([^;]+);', properties)
|
||||||
|
if bg_match:
|
||||||
|
bg_color = bg_match.group(1).strip()
|
||||||
|
|
||||||
|
color_match = re.search(r'(?<!background-)color:\s*([^;]+);', properties)
|
||||||
|
if color_match:
|
||||||
|
text_color = color_match.group(1).strip()
|
||||||
|
|
||||||
|
if bg_color and text_color:
|
||||||
|
# Resolve variables
|
||||||
|
if bg_color.startswith('var('):
|
||||||
|
var_match = re.search(r'var\(([^)]+)\)', bg_color)
|
||||||
|
if var_match:
|
||||||
|
var_name = var_match.group(1)
|
||||||
|
if var_name in root_vars:
|
||||||
|
bg_color = root_vars[var_name]
|
||||||
|
|
||||||
|
if text_color.startswith('var('):
|
||||||
|
var_match = re.search(r'var\(([^)]+)\)', text_color)
|
||||||
|
if var_match:
|
||||||
|
var_name = var_match.group(1)
|
||||||
|
if var_name in root_vars:
|
||||||
|
text_color = root_vars[var_name]
|
||||||
|
|
||||||
|
# Only add hex colors for now
|
||||||
|
if bg_color.startswith('#') and text_color.startswith('#'):
|
||||||
|
color_pairs.append((selector, bg_color, text_color))
|
||||||
|
|
||||||
|
return color_pairs, root_vars
|
||||||
|
|
||||||
|
def check_contrast_ratio(color_pairs):
|
||||||
|
"""Check contrast ratios for color pairs"""
|
||||||
|
results = []
|
||||||
|
for selector, bg_color, text_color in color_pairs:
|
||||||
|
try:
|
||||||
|
bg_rgb = hex_to_rgb(bg_color)
|
||||||
|
text_rgb = hex_to_rgb(text_color)
|
||||||
|
|
||||||
|
bg_luminance = luminance(*bg_rgb)
|
||||||
|
text_luminance = luminance(*text_rgb)
|
||||||
|
|
||||||
|
ratio = contrast_ratio(bg_luminance, text_luminance)
|
||||||
|
|
||||||
|
status = "PASS" if ratio >= 4.5 else "FAIL"
|
||||||
|
if 3.0 <= ratio < 4.5:
|
||||||
|
status = "PASS (Large Text Only)"
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"selector": selector,
|
||||||
|
"background": bg_color,
|
||||||
|
"text": text_color,
|
||||||
|
"ratio": ratio,
|
||||||
|
"status": status
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing {selector}: {e}")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def main():
|
||||||
|
css_file = "docker/ploughshares/static/css/custom.css"
|
||||||
|
|
||||||
|
if not os.path.exists(css_file):
|
||||||
|
print(f"CSS file not found: {css_file}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Analyzing contrast ratios in {css_file}...")
|
||||||
|
color_pairs, root_vars = parse_css_colors(css_file)
|
||||||
|
|
||||||
|
print("\nCSS Variables:")
|
||||||
|
for name, value in root_vars.items():
|
||||||
|
print(f" {name}: {value}")
|
||||||
|
|
||||||
|
print("\nContrast Ratio Analysis:")
|
||||||
|
results = check_contrast_ratio(color_pairs)
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
print("No color pairs found for contrast analysis.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Sort by ratio (ascending)
|
||||||
|
results.sort(key=lambda x: x["ratio"])
|
||||||
|
|
||||||
|
print(f"\n{'Selector':<30} {'Background':<15} {'Text':<15} {'Ratio':<10} {'Status'}")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
print(f"{result['selector']:<30} {result['background']:<15} {result['text']:<15} {result['ratio']:.2f} {result['status']}")
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
fails = sum(1 for r in results if r['status'] == 'FAIL')
|
||||||
|
large_only = sum(1 for r in results if r['status'] == 'PASS (Large Text Only)')
|
||||||
|
passes = sum(1 for r in results if r['status'] == 'PASS')
|
||||||
|
|
||||||
|
print("\nSummary:")
|
||||||
|
print(f" Total color pairs analyzed: {len(results)}")
|
||||||
|
print(f" Passed: {passes}")
|
||||||
|
print(f" Passed (Large Text Only): {large_only}")
|
||||||
|
print(f" Failed: {fails}")
|
||||||
|
|
||||||
|
print("\nRecommendations:")
|
||||||
|
if fails > 0:
|
||||||
|
print(" - Increase contrast for failing elements to at least 4.5:1")
|
||||||
|
if large_only > 0:
|
||||||
|
print(" - Ensure text with lower contrast (3:1-4.5:1) is used only for large text (18pt+)")
|
||||||
|
print(" - Use WebAIM Contrast Checker for verification: https://webaim.org/resources/contrastchecker/")
|
||||||
|
print(" - Test with real users, including those with visual impairments")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if Lighthouse is installed
|
||||||
|
if ! command -v lighthouse &> /dev/null; then
|
||||||
|
echo "Lighthouse CLI is not installed. Installing now..."
|
||||||
|
npm install -g lighthouse
|
||||||
|
fi
|
||||||
|
|
||||||
|
# URL to test
|
||||||
|
URL="http://localhost:5005"
|
||||||
|
|
||||||
|
# Run Lighthouse with focus on accessibility
|
||||||
|
echo "Running Lighthouse accessibility test on $URL"
|
||||||
|
lighthouse $URL --only-categories=accessibility --output=html --output-path=./lighthouse-report.html --chrome-flags="--headless --disable-gpu --no-sandbox"
|
||||||
|
|
||||||
|
# Open the report
|
||||||
|
echo "Test completed. Opening report..."
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# macOS
|
||||||
|
open ./lighthouse-report.html
|
||||||
|
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||||
|
# Linux
|
||||||
|
xdg-open ./lighthouse-report.html
|
||||||
|
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
|
||||||
|
# Windows
|
||||||
|
start ./lighthouse-report.html
|
||||||
|
else
|
||||||
|
echo "Report saved to ./lighthouse-report.html"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Manual testing recommendations:"
|
||||||
|
echo "1. Check contrast ratio using WebAIM Contrast Checker: https://webaim.org/resources/contrastchecker/"
|
||||||
|
echo "2. Test keyboard navigation through the site"
|
||||||
|
echo "3. Verify screen reader compatibility"
|
||||||
|
echo "4. Check form field labels and ARIA attributes"
|
|
@ -17,7 +17,7 @@ services:
|
||||||
- POSTGRES_DB=ploughshares
|
- POSTGRES_DB=ploughshares
|
||||||
- POSTGRES_USER=ploughshares
|
- POSTGRES_USER=ploughshares
|
||||||
- POSTGRES_PASSWORD=ploughshares_password
|
- POSTGRES_PASSWORD=ploughshares_password
|
||||||
- APP_VERSION=0.1.2
|
- APP_VERSION=0.2.0
|
||||||
- APP_ENV=development
|
- APP_ENV=development
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
|
|
|
@ -443,6 +443,13 @@ def update_transaction(id):
|
||||||
|
|
||||||
return render_template('transaction_form.html', transaction=transaction, version=VERSION)
|
return render_template('transaction_form.html', transaction=transaction, version=VERSION)
|
||||||
|
|
||||||
|
@app.route('/accessibility')
|
||||||
|
def accessibility_test():
|
||||||
|
"""
|
||||||
|
Route for testing accessibility features
|
||||||
|
"""
|
||||||
|
return render_template('accessibility.html', version=app.config['VERSION'])
|
||||||
|
|
||||||
def bootstrap_database():
|
def bootstrap_database():
|
||||||
"""
|
"""
|
||||||
Checks if the database is empty and initializes the schema if needed.
|
Checks if the database is empty and initializes the schema if needed.
|
||||||
|
|
|
@ -5,11 +5,31 @@ body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
|
color: #333333; /* Improved text contrast */
|
||||||
|
line-height: 1.5; /* Improved readability */
|
||||||
|
font-size: 16px; /* Base font size for better readability */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ploughshares branding colors with improved contrast */
|
||||||
|
:root {
|
||||||
|
--ploughshares-blue: #2d5b96; /* Lightened blue for better readability */
|
||||||
|
--ploughshares-dark-blue: #1b365d; /* Original blue kept as dark variant */
|
||||||
|
--ploughshares-light-blue: #6b93c3; /* Lighter blue for better contrast */
|
||||||
|
--ploughshares-accent: #c4d600;
|
||||||
|
--ploughshares-dark-accent: #9aa800; /* Darker accent for better contrast */
|
||||||
|
--text-on-dark: #ffffff;
|
||||||
|
--text-on-light: #333333;
|
||||||
|
--link-color: #0056b3; /* High contrast link color */
|
||||||
|
--link-hover: #003d7a; /* Darker on hover for better feedback */
|
||||||
|
--success-color: #2e7d32; /* Accessible green */
|
||||||
|
--warning-color: #e65100; /* Accessible orange */
|
||||||
|
--info-color: #0277bd; /* Accessible blue */
|
||||||
|
--error-color: #c62828; /* Accessible red */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header styles */
|
/* Header styles */
|
||||||
.header {
|
.header {
|
||||||
border-bottom: 1px solid #e5e5e5;
|
border-bottom: 1px solid #d0d0d0;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,13 +37,14 @@ body {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
|
color: var(--text-on-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer styles */
|
/* Footer styles */
|
||||||
.footer {
|
.footer {
|
||||||
padding-top: 19px;
|
padding-top: 19px;
|
||||||
color: #777;
|
color: #555555; /* Improved contrast from #777 */
|
||||||
border-top: 1px solid #e5e5e5;
|
border-top: 1px solid #d0d0d0;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +53,30 @@ body {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: var(--ploughshares-light-blue);
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(74, 122, 171, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Improved form labels for accessibility */
|
||||||
|
label {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
/* Card styles */
|
/* Card styles */
|
||||||
|
.card {
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid #d0d0d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background-color: var(--ploughshares-blue);
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.document-card {
|
.document-card {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
@ -47,17 +91,25 @@ body {
|
||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
width: auto;
|
width: auto;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
/* Improved logo contrast with background */
|
||||||
|
background-color: white;
|
||||||
|
padding: 3px;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer img {
|
.footer img {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
/* Improved logo contrast with background */
|
||||||
|
background-color: white;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Currency styles */
|
/* Currency styles */
|
||||||
.currency-value {
|
.currency-value {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #28a745;
|
color: var(--success-color); /* More accessible green */
|
||||||
}
|
}
|
||||||
|
|
||||||
.amount-cell {
|
.amount-cell {
|
||||||
|
@ -71,7 +123,7 @@ td.amount-cell {
|
||||||
/* Version display */
|
/* Version display */
|
||||||
.version {
|
.version {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
color: #999;
|
color: #555555; /* Improved contrast */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navigation styles */
|
/* Navigation styles */
|
||||||
|
@ -83,30 +135,105 @@ td.amount-cell {
|
||||||
margin: 0 0.5rem;
|
margin: 0 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Button icons using Unicode symbols */
|
/* Link styles with improved accessibility */
|
||||||
.btn-add::before {
|
a {
|
||||||
content: "➕ ";
|
color: var(--link-color);
|
||||||
|
text-decoration: underline; /* Always underline links for better visibility */
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-edit::before {
|
a:hover, a:focus {
|
||||||
content: "✏️ ";
|
color: var(--link-hover);
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-delete::before {
|
/* Button styles with improved accessibility */
|
||||||
content: "🗑️ ";
|
.btn {
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-view::before {
|
.btn-primary {
|
||||||
content: "👁️ ";
|
background-color: var(--ploughshares-blue);
|
||||||
|
border-color: var(--ploughshares-blue);
|
||||||
|
color: white; /* Ensure text contrast */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navbar toggle button */
|
.btn-primary:hover,
|
||||||
.navbar-toggler-icon {
|
.btn-primary:focus {
|
||||||
|
background-color: var(--ploughshares-dark-blue); /* Darker for hover state */
|
||||||
|
border-color: var(--ploughshares-dark-blue);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background-color: var(--success-color);
|
||||||
|
border-color: var(--success-color);
|
||||||
|
color: white; /* Ensure text contrast */
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background-color: var(--warning-color);
|
||||||
|
border-color: var(--warning-color);
|
||||||
|
color: white; /* Ensure text contrast */
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
background-color: var(--info-color);
|
||||||
|
border-color: var(--info-color);
|
||||||
|
color: white; /* Ensure text contrast */
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: var(--error-color);
|
||||||
|
border-color: var(--error-color);
|
||||||
|
color: white; /* Ensure text contrast */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus states for accessibility */
|
||||||
|
a:focus, button:focus, input:focus, select:focus, textarea:focus, [tabindex]:focus {
|
||||||
|
outline: 3px solid var(--ploughshares-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button icons using Bootstrap Icons */
|
||||||
|
.btn-add i, .btn-edit i, .btn-delete i, .btn-view i {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navbar styles */
|
||||||
|
.navbar {
|
||||||
|
background-color: var(--ploughshares-blue) !important;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-light .navbar-brand {
|
||||||
|
color: var(--text-on-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-light .navbar-nav .nav-link {
|
||||||
|
color: rgba(255, 255, 255, 0.9); /* Improved contrast from white */
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-light .navbar-nav .nav-link:hover,
|
||||||
|
.navbar-light .navbar-nav .nav-link:focus {
|
||||||
|
color: var(--ploughshares-accent);
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-light .navbar-toggler {
|
||||||
|
border-color: rgba(255, 255, 255, 0.7); /* Improved contrast */
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-light .navbar-toggler-icon {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-toggler-icon::before {
|
.navbar-light .navbar-toggler-icon::before {
|
||||||
content: "☰";
|
content: "☰";
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -117,37 +244,74 @@ td.amount-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
color: rgba(255, 255, 255, 0.9); /* Improved contrast */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ploughshares branding colors */
|
/* Table styles with improved accessibility */
|
||||||
:root {
|
.table {
|
||||||
--ploughshares-blue: #1b365d;
|
border-collapse: collapse;
|
||||||
--ploughshares-light-blue: #4a7aab;
|
width: 100%;
|
||||||
--ploughshares-accent: #c4d600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.table th {
|
||||||
background-color: var(--ploughshares-blue) !important;
|
background-color: var(--ploughshares-blue);
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-light .navbar-nav .nav-link {
|
|
||||||
color: white;
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
border-bottom: 2px solid var(--ploughshares-dark-blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-light .navbar-nav .nav-link:hover {
|
.table td {
|
||||||
color: var(--ploughshares-accent);
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
padding: 0.75rem;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-light .navbar-toggler {
|
.table tbody tr:hover {
|
||||||
border-color: rgba(255,255,255,0.5);
|
background-color: rgba(45, 91, 150, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-light .navbar-toggler-icon::before {
|
/* Skip to content link for keyboard users */
|
||||||
|
.skip-link {
|
||||||
|
position: absolute;
|
||||||
|
top: -40px;
|
||||||
|
left: 0;
|
||||||
|
background: var(--ploughshares-blue);
|
||||||
color: white;
|
color: white;
|
||||||
|
padding: 8px;
|
||||||
|
z-index: 100;
|
||||||
|
transition: top 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.skip-link:focus {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* High contrast mode support */
|
||||||
|
@media (forced-colors: active) {
|
||||||
|
.btn {
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.navbar-brand img {
|
.navbar-brand img {
|
||||||
max-height: 30px;
|
max-height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 0.375rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-size: 15px; /* Slightly smaller font on mobile but still readable */
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,248 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Accessibility Testing - Project Ploughshares{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>Accessibility Testing Page</h1>
|
||||||
|
<p class="lead">This page demonstrates the accessible components and allows for easy testing of accessibility features.</p>
|
||||||
|
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
<strong>New!</strong> Click the "Contrast Checker" button in the bottom right corner to test color contrast ratios.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2>Color Contrast</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="h5 mb-0">Text on Background Colors</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>Regular text on white background (Default)</p>
|
||||||
|
<p style="background-color: var(--ploughshares-blue); color: white; padding: 10px;">White text on Ploughshares blue</p>
|
||||||
|
<p style="background-color: var(--ploughshares-dark-blue); color: white; padding: 10px;">White text on Ploughshares dark blue</p>
|
||||||
|
<p style="background-color: var(--ploughshares-light-blue); color: black; padding: 10px;">Black text on Ploughshares light blue</p>
|
||||||
|
<p style="background-color: var(--ploughshares-accent); color: black; padding: 10px;">Black text on Ploughshares accent</p>
|
||||||
|
<p style="background-color: var(--success-color); color: white; padding: 10px;">White text on success color</p>
|
||||||
|
<p style="background-color: var(--warning-color); color: white; padding: 10px;">White text on warning color</p>
|
||||||
|
<p style="background-color: var(--error-color); color: white; padding: 10px;">White text on error color</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="h5 mb-0">Interactive Elements</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p><a href="#">This is a regular link</a></p>
|
||||||
|
<p><a href="#" class="btn btn-primary">Primary Button</a></p>
|
||||||
|
<p><a href="#" class="btn btn-success">Success Button</a></p>
|
||||||
|
<p><a href="#" class="btn btn-warning">Warning Button</a></p>
|
||||||
|
<p><a href="#" class="btn btn-info">Info Button</a></p>
|
||||||
|
<p><a href="#" class="btn btn-danger">Danger Button</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2>Keyboard Navigation</h2>
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="h5 mb-0">Focus States</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>Tab through these elements to test focus visibility:</p>
|
||||||
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||||
|
<a href="#" class="btn btn-primary">Button 1</a>
|
||||||
|
<a href="#" class="btn btn-primary">Button 2</a>
|
||||||
|
<a href="#" class="btn btn-primary">Button 3</a>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="test-input" class="form-label">Test Input</label>
|
||||||
|
<input type="text" class="form-control" id="test-input" placeholder="Focus me">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="test-select" class="form-label">Test Select</label>
|
||||||
|
<select class="form-select" id="test-select">
|
||||||
|
<option>Option 1</option>
|
||||||
|
<option>Option 2</option>
|
||||||
|
<option>Option 3</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2>Form Controls</h2>
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="h5 mb-0">Accessible Form</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" aria-describedby="nameHelp">
|
||||||
|
<div id="nameHelp" class="form-text">Enter your full name.</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email" class="form-label">Email address</label>
|
||||||
|
<input type="email" class="form-control" id="email" aria-describedby="emailHelp">
|
||||||
|
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="message" class="form-label">Message</label>
|
||||||
|
<textarea class="form-control" id="message" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="exampleCheck1">
|
||||||
|
<label class="form-check-label" for="exampleCheck1">Check me out</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2>Tables</h2>
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="h5 mb-0">Accessible Table</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<caption>Sample transactions data</caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">Transaction</th>
|
||||||
|
<th scope="col">Date</th>
|
||||||
|
<th scope="col" class="amount-cell">Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">1</th>
|
||||||
|
<td>Equipment Purchase</td>
|
||||||
|
<td>2023-01-15</td>
|
||||||
|
<td class="amount-cell currency-value">$1,200.00</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">2</th>
|
||||||
|
<td>Consulting Services</td>
|
||||||
|
<td>2023-02-28</td>
|
||||||
|
<td class="amount-cell currency-value">$3,500.00</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">3</th>
|
||||||
|
<td>Software License</td>
|
||||||
|
<td>2023-03-10</td>
|
||||||
|
<td class="amount-cell currency-value">$950.00</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2>Icons and Images</h2>
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="h5 mb-0">Accessible Icons</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>Icons with proper alternative text:</p>
|
||||||
|
<div class="d-flex flex-wrap gap-3">
|
||||||
|
<button class="btn btn-primary">
|
||||||
|
<i class="bi bi-plus-circle" aria-hidden="true"></i> Add
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-info">
|
||||||
|
<i class="bi bi-pencil" aria-hidden="true"></i> Edit
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger">
|
||||||
|
<i class="bi bi-trash" aria-hidden="true"></i> Delete
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary">
|
||||||
|
<i class="bi bi-eye" aria-hidden="true"></i> View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-4">Icon-only buttons with aria-label:</p>
|
||||||
|
<div class="d-flex flex-wrap gap-3">
|
||||||
|
<button class="btn btn-primary" aria-label="Add item">
|
||||||
|
<i class="bi bi-plus-circle" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-info" aria-label="Edit item">
|
||||||
|
<i class="bi bi-pencil" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger" aria-label="Delete item">
|
||||||
|
<i class="bi bi-trash" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" aria-label="View item">
|
||||||
|
<i class="bi bi-eye" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2>Text Sizing</h2>
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="h5 mb-0">Text Hierarchy</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h1>Heading Level 1</h1>
|
||||||
|
<h2>Heading Level 2</h2>
|
||||||
|
<h3>Heading Level 3</h3>
|
||||||
|
<h4>Heading Level 4</h4>
|
||||||
|
<h5>Heading Level 5</h5>
|
||||||
|
<h6>Heading Level 6</h6>
|
||||||
|
<p>This is regular paragraph text. The base font size is set to 16px for optimal readability.</p>
|
||||||
|
<p class="lead">This is a lead paragraph with slightly larger text.</p>
|
||||||
|
<p><small>This is smaller text, still maintaining readability.</small></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="{{ url_for('static', filename='js/contrast-checker.js') }}"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Add a message when the skip link is used
|
||||||
|
const skipLink = document.querySelector('.skip-link');
|
||||||
|
if (skipLink) {
|
||||||
|
skipLink.addEventListener('click', function() {
|
||||||
|
const alertDiv = document.createElement('div');
|
||||||
|
alertDiv.className = 'alert alert-success alert-dismissible fade show';
|
||||||
|
alertDiv.setAttribute('role', 'alert');
|
||||||
|
alertDiv.innerHTML = 'Skip link used successfully! This alert is for demonstration purposes.';
|
||||||
|
|
||||||
|
const closeButton = document.createElement('button');
|
||||||
|
closeButton.className = 'btn-close';
|
||||||
|
closeButton.setAttribute('type', 'button');
|
||||||
|
closeButton.setAttribute('data-bs-dismiss', 'alert');
|
||||||
|
closeButton.setAttribute('aria-label', 'Close');
|
||||||
|
|
||||||
|
alertDiv.appendChild(closeButton);
|
||||||
|
|
||||||
|
const contentDiv = document.querySelector('.content');
|
||||||
|
contentDiv.insertBefore(alertDiv, contentDiv.firstChild);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -3,6 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="Project Ploughshares Transaction Management System - Track and manage transaction data">
|
||||||
<title>{% block title %}Project Ploughshares - Transaction Management System{% endblock %}</title>
|
<title>{% block title %}Project Ploughshares - Transaction Management System{% endblock %}</title>
|
||||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
<link rel="apple-touch-icon" href="{{ url_for('static', filename='img/logo-icon.png') }}">
|
<link rel="apple-touch-icon" href="{{ url_for('static', filename='img/logo-icon.png') }}">
|
||||||
|
@ -21,13 +22,16 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Skip link for keyboard accessibility -->
|
||||||
|
<a href="#main-content" class="skip-link">Skip to main content</a>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header clearfix">
|
<header class="header clearfix">
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
<a class="navbar-brand" href="{{ url_for('index') }}" aria-label="Project Ploughshares Home">
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
<img src="{{ url_for('static', filename='img/logo.png') }}" alt="Project Ploughshares Logo">
|
<img src="{{ url_for('static', filename='img/logo.png') }}" alt="Project Ploughshares Logo" width="180" height="40">
|
||||||
<span class="d-none d-md-inline">Transaction Management System</span>
|
<span class="d-none d-md-inline">Transaction Management System</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -37,43 +41,71 @@
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
<ul class="navbar-nav ms-auto">
|
<ul class="navbar-nav ms-auto">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('index') }}">Home</a>
|
<a class="nav-link" href="{{ url_for('index') }}" aria-current="{% if request.endpoint == 'index' %}page{% endif %}">
|
||||||
|
<i class="bi bi-house-door" aria-hidden="true"></i> Home
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('create_transaction') }}">New Transaction</a>
|
<a class="nav-link" href="{{ url_for('create_transaction') }}" aria-current="{% if request.endpoint == 'create_transaction' %}page{% endif %}">
|
||||||
|
<i class="bi bi-plus-circle" aria-hidden="true"></i> New Transaction
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('api_docs') }}">API Documentation</a>
|
<a class="nav-link" href="{{ url_for('api_docs') }}" aria-current="{% if request.endpoint == 'api_docs' %}page{% endif %}">
|
||||||
|
<i class="bi bi-file-earmark-text" aria-hidden="true"></i> API Documentation
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
{% with messages = get_flashed_messages() %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="alert alert-info" role="alert">
|
<div class="alert-container" role="alert">
|
||||||
{% for message in messages %}
|
{% for category, message in messages %}
|
||||||
{{ message }}
|
<div class="alert alert-{{ category if category else 'info' }}" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
<div class="content">
|
<main id="main-content" class="content">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<p>© 2023 Project Ploughshares - Transaction Management System <span class="version">v{{ version }}</span></p>
|
<p>© 2023 Project Ploughshares - Transaction Management System <span class="version">v{{ version }}</span></p>
|
||||||
<img src="{{ url_for('static', filename='img/logo-icon.png') }}" alt="Project Ploughshares" style="height: 24px;">
|
<img src="{{ url_for('static', filename='img/logo-icon.png') }}" alt="Project Ploughshares" width="24" height="24">
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
|
<script>
|
||||||
|
// Add ARIA attributes to dynamically created elements
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Make sure all buttons have accessible labels
|
||||||
|
document.querySelectorAll('button').forEach(function(button) {
|
||||||
|
if (!button.getAttribute('aria-label') && !button.textContent.trim()) {
|
||||||
|
const icon = button.querySelector('i, .bi');
|
||||||
|
if (icon) {
|
||||||
|
const iconClass = icon.className;
|
||||||
|
if (iconClass.includes('edit')) button.setAttribute('aria-label', 'Edit');
|
||||||
|
else if (iconClass.includes('delete')) button.setAttribute('aria-label', 'Delete');
|
||||||
|
else if (iconClass.includes('view')) button.setAttribute('aria-label', 'View');
|
||||||
|
else if (iconClass.includes('add')) button.setAttribute('aria-label', 'Add');
|
||||||
|
else button.setAttribute('aria-label', 'Button');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simple script to run Lighthouse tests on the local server
|
||||||
|
|
||||||
|
echo "Running Lighthouse accessibility test on http://localhost:5005"
|
||||||
|
echo "Please open Chrome and navigate to http://localhost:5005"
|
||||||
|
echo ""
|
||||||
|
echo "Then follow these steps to run Lighthouse:"
|
||||||
|
echo "1. Right-click and select 'Inspect' or press F12"
|
||||||
|
echo "2. Click on the 'Lighthouse' tab in the DevTools panel"
|
||||||
|
echo "3. Check 'Accessibility' category"
|
||||||
|
echo "4. Click 'Generate report'"
|
||||||
|
echo ""
|
||||||
|
echo "Common accessibility issues to look for:"
|
||||||
|
echo "- Contrast ratio issues (text should have at least 4.5:1 contrast with background)"
|
||||||
|
echo "- Missing alt text on images"
|
||||||
|
echo "- Missing form labels"
|
||||||
|
echo "- Keyboard navigation issues"
|
||||||
|
echo "- ARIA attributes that are incorrect or missing"
|
||||||
|
echo ""
|
||||||
|
echo "Testing URLs:"
|
||||||
|
echo "- Main page: http://localhost:5005/"
|
||||||
|
echo "- Accessibility test page: http://localhost:5005/accessibility"
|
||||||
|
echo ""
|
||||||
|
echo "You can also use the Web Accessibility Evaluation Tool (WAVE):"
|
||||||
|
echo "https://wave.webaim.org/report#/http://localhost:5005"
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 610 B |
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
|
@ -1,2 +1,3 @@
|
||||||
Thu Jul 3 13:33:04 EDT 2025: Version changed from 0.1.0 to 0.1.1
|
Thu Jul 3 13:33:04 EDT 2025: Version changed from 0.1.0 to 0.1.1
|
||||||
Thu Jul 3 13:40:53 EDT 2025: Version changed from 0.1.1 to 0.1.2
|
Thu Jul 3 13:40:53 EDT 2025: Version changed from 0.1.1 to 0.1.2
|
||||||
|
Fri Jul 4 16:04:29 EDT 2025: Version changed from 0.1.2 to 0.2.0
|
||||||
|
|
Loading…
Reference in New Issue