From 45f07f763f427a865ae4f9ec6f0ee2525c9da8d8 Mon Sep 17 00:00:00 2001 From: VinnyNC Date: Mon, 29 Sep 2025 23:02:09 -0400 Subject: [PATCH 1/2] Complete 4.2.1: WCAG AA compliant interactive state implementation --- UI_UPDATE.MD | 6 +- static/css/components.css | 139 +++++++++++++++++++++++++++++++++++++- static/css/variables.css | 1 + 3 files changed, 140 insertions(+), 6 deletions(-) diff --git a/UI_UPDATE.MD b/UI_UPDATE.MD index 7a06d9f..8d58f45 100644 --- a/UI_UPDATE.MD +++ b/UI_UPDATE.MD @@ -461,9 +461,9 @@ All utilities follow the established pattern of using CSS custom properties from ### Sub-task 4.2: Interactive States and Feedback #### 4.2.1: State Implementation -- [ ] Design comprehensive hover states -- [ ] Implement focus indicators meeting WCAG standards -- [ ] Add active and pressed state styling +- [x] Design comprehensive hover states +- [x] Implement focus indicators meeting WCAG standards +- [x] Add active and pressed state styling #### 4.2.2: Form Validation - [ ] Implement visual form validation feedback diff --git a/static/css/components.css b/static/css/components.css index 9056ff8..ea1c1d0 100644 --- a/static/css/components.css +++ b/static/css/components.css @@ -123,10 +123,11 @@ background: rgba(var(--color-primary-rgb), 0.1); } -/* Focus states for accessibility */ +/* Focus states for accessibility - WCAG AA compliant */ .btn:focus-visible { - outline: 2px solid var(--color-focus); + outline: 3px solid var(--color-focus); outline-offset: 2px; + box-shadow: 0 0 0 1px var(--color-surface), 0 0 0 4px var(--dodgers-blue-100); } /* Loading state */ @@ -225,8 +226,68 @@ } .card:focus-visible { - outline: 2px solid var(--color-focus); + outline: 3px solid var(--color-focus); outline-offset: 2px; + box-shadow: 0 0 0 2px var(--dodgers-blue-100); +} + +/* Form focus states - WCAG AA compliant */ +.form-control:focus, +.form-input:focus, +.form-textarea:focus, +.form-select:focus { + outline: 3px solid var(--color-focus); + outline-offset: 2px; + box-shadow: 0 0 0 2px var(--dodgers-blue-100); + border-color: var(--color-focus); +} + +.chat__input input:focus { + outline: 3px solid var(--color-focus); + outline-offset: 2px; + box-shadow: 0 0 0 2px var(--dodgers-blue-100); +} + +/* Link focus states */ +a:focus-visible { + outline: 3px solid var(--color-focus); + outline-offset: 2px; + text-decoration: underline; +} + +/* Dashboard and control focus states */ +.dashboard__toggle-btn:focus-visible { + outline: 3px solid var(--dodgers-white); + outline-offset: 2px; + background: rgba(255,255,255,0.3); +} + +.video-player__quality-selector:focus-visible, +.video-player__toggle-chat-btn:focus-visible, +.stream-stats__refresh-btn:focus-visible { + outline: 3px solid var(--dodgers-white); + outline-offset: 2px; + background: rgba(255,255,255,0.3); +} + +/* Chat admin controls focus */ +.chat__admin-btn:focus-visible { + outline: 3px solid var(--dodgers-gold); + outline-offset: 1px; +} + +/* Mobile navigation focus */ +.mobile-nav-btn:focus-visible { + outline: 3px solid var(--dodgers-white); + outline-offset: 2px; + background: rgba(255,255,255,0.15); +} + +/* FAB focus */ +.fab:focus-visible { + outline: 3px solid var(--dodgers-white); + outline-offset: 2px; + box-shadow: 0 0 0 1px var(--dodgers-blue-400), var(--elevation-5); } /* Card variants */ @@ -1754,6 +1815,78 @@ .chat__input button:active { transform: translateY(0); + box-shadow: var(--elevation-1); +} + +/* Active states for links and form elements */ +a:active { + transform: scale(0.98); +} + +/* Form elements pressed states */ +.form-control:active { + transform: translateY(0); +} + +.form-input:active, +.form-textarea:active, +.form-select:active { + transform: translateY(0); + box-shadow: 0 0 0 2px var(--dodgers-blue-200); +} + +/* Dashboard controls active states */ +.dashboard__toggle-btn:active { + transform: translateY(0); + background: rgba(255,255,255,0.4); +} + +.stats-card:active { + transform: translateY(-1px); + box-shadow: var(--elevation-2); +} + +/* Video controls pressed states */ +.video-player__quality-selector:active, +.video-player__toggle-chat-btn:active, +.stream-stats__refresh-btn:active { + transform: translateY(0); + background: rgba(255,255,255,0.3); +} + +/* Chat admin controls active states */ +.chat__admin-btn:active { + transform: translateY(0); + background: rgba(255,215,0,0.6); +} + +/* Message actions pressed states */ +.delete-btn:active, .ban-btn:active { + transform: scale(0.95); +} + +/* Mobile navigation active states */ +.mobile-nav-btn:active { + transform: scale(0.95) translateY(0); + background: rgba(255,255,255,0.15); +} + +/* FAB active states */ +.fab:active, .fab.secondary:active { + transform: scale(1.05); + box-shadow: var(--elevation-3); +} + +/* Toast notifications active states */ +.toast:active { + transform: translateX(-50%) scale(0.95); +} + +/* Activity and user item active states */ +.activity-item:active, +.user-item:active { + transform: scale(0.98); + background: var(--color-surface-variant); } .chat__empty-state { diff --git a/static/css/variables.css b/static/css/variables.css index 1f9d341..7ff7468 100644 --- a/static/css/variables.css +++ b/static/css/variables.css @@ -109,6 +109,7 @@ --hover-overlay: rgba(255, 255, 255, 0.1); --active-overlay: rgba(255, 255, 255, 0.2); --focus-ring: rgba(0, 90, 156, 0.5); + --color-focus: var(--dodgers-blue-400); /* WCAG AA compliant focus color */ /* Borders & Dividers */ --border-color: #1a2332; From 54db215848671ea5dff8c36d5fbe664c3c01da19 Mon Sep 17 00:00:00 2001 From: VinnyNC Date: Mon, 29 Sep 2025 23:17:11 -0400 Subject: [PATCH 2/2] Complete 4.2.2: Form Validation Implementation - Add form-group wrappers and error message elements to HTML - Implement comprehensive client-side validation for nickname and message inputs - Add visual feedback using existing CSS validation classes - Include success confirmation states and proper error handling - Enhance accessibility with focus management and screen reader support --- UI_UPDATE.MD | 16 ++++-- assets/js/chat.js | 123 ++++++++++++++++++++++++++++++++++++++++------ index.php | 16 ++++-- 3 files changed, 131 insertions(+), 24 deletions(-) diff --git a/UI_UPDATE.MD b/UI_UPDATE.MD index 8d58f45..efb55ca 100644 --- a/UI_UPDATE.MD +++ b/UI_UPDATE.MD @@ -465,10 +465,18 @@ All utilities follow the established pattern of using CSS custom properties from - [x] Implement focus indicators meeting WCAG standards - [x] Add active and pressed state styling -#### 4.2.2: Form Validation -- [ ] Implement visual form validation feedback -- [ ] Add error state styling -- [ ] Create success confirmation states +#### 4.2.2: Form Validation - COMPLETED 9/29/2025 +- [x] Implement visual form validation feedback +- [x] Add error state styling +- [x] Create success confirmation states + +**Notes:** Comprehensive form validation system implemented for chat inputs including: +- HTML structure updates: Added form-group wrappers with error message elements for nickname and message inputs +- Enhanced JavaScript validation: Added client-side validation with detailed error messages for nickname (1-20 chars, alphanumeric/places) and message (1-1000 chars) requirements +- CSS integration: Utilizes existing .form-group--error and .form-group--success classes for visual feedback +- User experience: Error messages display below inputs, success states provide green styling and toast confirmation, focus management ensures good UX flow +- Validation coverage: Empty inputs, character length limits, nickname character restrictions, and server error handling +- Accessibility: Proper error messaging and focus management for screen readers and keyboard navigation ### Sub-task 4.3: Enhanced User Feedback Systems diff --git a/assets/js/chat.js b/assets/js/chat.js index ec289b4..0991fb8 100644 --- a/assets/js/chat.js +++ b/assets/js/chat.js @@ -76,44 +76,137 @@ }); } + // Validation helper functions + function validateNickname(nickname) { + if (!nickname) { + return { valid: false, message: 'Nickname is required' }; + } + + const trimmed = nickname.trim(); + if (trimmed.length === 0) { + return { valid: false, message: 'Nickname cannot be empty' }; + } + + if (trimmed.length > 20) { + return { valid: false, message: 'Nickname must be 20 characters or less' }; + } + + if (trimmed.length < 1) { + return { valid: false, message: 'Nickname must be at least 1 character' }; + } + + // Allow alphanumeric characters and spaces, apostrophes, hyphens + const nicknameRegex = /^[a-zA-Z0-9\s'-]+$/; + if (!nicknameRegex.test(trimmed)) { + return { valid: false, message: 'Nickname can only contain letters, numbers, spaces, hyphens, and apostrophes' }; + } + + return { valid: true }; + } + + function validateMessage(message) { + if (!message) { + return { valid: false, message: 'Message is required' }; + } + + const trimmed = message.trim(); + if (trimmed.length === 0) { + return { valid: false, message: 'Message cannot be empty' }; + } + + if (trimmed.length > 1000) { + return { valid: false, message: 'Message must be 1000 characters or less' }; + } + + return { valid: true }; + } + + function updateValidationUI(field, errorElement, isValid, errorMessage) { + const formGroup = errorElement.closest('.form-group'); + const input = formGroup.querySelector('input'); + + formGroup.classList.remove('form-group--error', 'form-group--success'); + + if (isValid) { + formGroup.classList.add('form-group--success'); + errorElement.style.display = 'none'; + } else { + formGroup.classList.add('form-group--error'); + errorElement.textContent = errorMessage; + errorElement.style.display = 'block'; + } + } + + function clearValidationState(field) { + const formGroup = document.querySelector(`#${field}`).closest('.form-group'); + if (formGroup) { + formGroup.classList.remove('form-group--error', 'form-group--success'); + const errorElement = document.getElementById(`${field}Error`); + if (errorElement) { + errorElement.style.display = 'none'; + } + } + } + // Send message functionality function sendMessage() { const messageInput = document.getElementById('messageInput'); const nicknameInput = document.getElementById('nickname'); + const nicknameErrorElement = document.getElementById('nicknameError'); + const messageErrorElement = document.getElementById('messageError'); + const message = messageInput?.value?.trim() || ''; const nickname = nicknameInput?.value?.trim() || ''; - if (!nickname) { - if (nicknameInput) { - nicknameInput.style.borderColor = 'var(--dodgers-red)'; - nicknameInput.focus(); - } - UIControls.showToast('Please enter a nickname'); - setTimeout(() => { - if (nicknameInput) nicknameInput.style.borderColor = '#e0e0e0'; - }, 2000); + // Validate nickname + const nicknameValidation = validateNickname(nickname); + updateValidationUI('nickname', nicknameErrorElement, nicknameValidation.valid, nicknameValidation.message); + + // Validate message + const messageValidation = validateMessage(message); + updateValidationUI('messageInput', messageErrorElement, messageValidation.valid, messageValidation.message); + + // If either validation failed, focus the appropriate field and stop + if (!nicknameValidation.valid) { + nicknameInput.focus(); return; } - if (!message) { - return; // Just return quietly for empty message + if (!messageValidation.valid) { + messageInput.focus(); + return; } - // Send message via API + // All validation passed - send message via API API.sendMessage(nickname, message) .then(data => { if (data.success) { + // Clear the form and show success if (messageInput) messageInput.value = ''; AppState.nickname = nickname; - // Message will be added automatically by polling + + // Show success confirmation + updateValidationUI('nickname', nicknameErrorElement, true); + updateValidationUI('messageInput', messageErrorElement, true); + + UIControls.showToast('Message sent successfully!'); + + // Clear success state after a delay + setTimeout(() => { + clearValidationState('nickname'); + clearValidationState('messageInput'); + }, 3000); + + AppLogger.log('Message sent successfully'); } else if (data.error) { - UIControls.showToast(data.error); + // Show server error + updateValidationUI('messageInput', messageErrorElement, false, 'Failed to send message: ' + data.error); UIControls.updateConnectionStatus(false); } }) .catch(error => { AppLogger.error('Error sending message:', error); - UIControls.showToast('Failed to send message'); + updateValidationUI('messageInput', messageErrorElement, false, 'Failed to send message. Please try again.'); UIControls.updateConnectionStatus(false); }); } diff --git a/index.php b/index.php index 20f2a52..df0c5f8 100644 --- a/index.php +++ b/index.php @@ -461,8 +461,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
Your ID: Loading...
-
- +
+
+ +
+
@@ -481,9 +484,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {

Send Message

-
- - +
+
+ + +
+