UNPKG

claritykit-svelte

Version:

A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility

364 lines (357 loc) 11.8 kB
/** * Crisis Mode Detection and Management Utility * * Provides crisis mode detection, automatic switching, and simplified interface patterns * for therapeutic components in high-stress or overwhelmed states. */ // Global crisis mode state let crisisModeState = { enabled: false, severity: 'mild', triggers: [], adaptations: [], timestamp: new Date() }; let crisisConfig = { autoDetect: true, userOverride: true, adaptationLevel: 'moderate', timeoutMinutes: 30, emergencyContacts: false }; /** * Crisis mode detection patterns */ export const crisisDetectionPatterns = { // Behavioral indicators rapidClicking: (clickCount, timeWindow) => clickCount > 10 && timeWindow < 2000, taskSwitching: (switchCount, timeWindow) => switchCount > 5 && timeWindow < 5000, errorRepeating: (errorCount, timeWindow) => errorCount > 3 && timeWindow < 10000, // Interaction patterns hesitationPattern: (hoverTime, clickDelay) => hoverTime > 3000 && clickDelay > 1000, abandonmentPattern: (partialInputs, timeWindow) => partialInputs > 3 && timeWindow < 60000, // Time-based patterns lateNightUsage: () => { const hour = new Date().getHours(); return hour >= 23 || hour <= 5; }, // Stress indicators keyboardMashing: (keyPressRate) => keyPressRate > 5, // keys per second mouseShaking: (movementVariance) => movementVariance > 100 }; /** * Activate crisis mode with specified trigger */ export function activateCrisisMode(trigger, severity = 'moderate') { crisisModeState = { ...crisisModeState, enabled: true, severity, triggers: [...crisisModeState.triggers, trigger], timestamp: new Date() }; // Apply automatic adaptations based on severity applyAutomaticAdaptations(severity); // Set automatic timeout setTimeout(() => { if (crisisModeState.enabled && !hasRecentTriggers()) { deactivateCrisisMode('timeout'); } }, crisisConfig.timeoutMinutes * 60 * 1000); // Announce to screen readers announceToScreenReader(`Crisis mode activated. Interface simplified for reduced cognitive load.`); // Notify listeners dispatchCrisisModeEvent('activated', crisisModeState); } /** * Deactivate crisis mode */ export function deactivateCrisisMode(reason = 'user') { crisisModeState = { ...crisisModeState, enabled: false, adaptations: [], timestamp: new Date() }; announceToScreenReader(`Crisis mode deactivated. Full interface restored.`); dispatchCrisisModeEvent('deactivated', { reason }); } /** * Check if crisis mode is currently active */ export function isCrisisModeActive() { return crisisModeState.enabled; } /** * Get current crisis mode severity */ export function getCrisisModeSeverity() { return crisisModeState.enabled ? crisisModeState.severity : null; } /** * Get crisis mode state (reactive) */ export function getCrisisModeState() { return crisisModeState; } /** * Update crisis mode configuration */ export function updateCrisisConfig(newConfig) { crisisConfig = { ...crisisConfig, ...newConfig }; } /** * Apply automatic adaptations based on severity level */ function applyAutomaticAdaptations(severity) { const adaptations = []; switch (severity) { case 'severe': adaptations.push({ componentType: 'all', adaptation: 'simplify', parameters: { maxOptions: 2, largeText: true, highContrast: true } }, { componentType: 'navigation', adaptation: 'simplify', parameters: { showOnlyEssential: true } }, { componentType: 'forms', adaptation: 'simplify', parameters: { oneFieldAtATime: true } }, { componentType: 'animations', adaptation: 'hide', parameters: {} }, { componentType: 'therapeutic', adaptation: 'emphasize', parameters: { showBreathingExercise: true } }); break; case 'moderate': adaptations.push({ componentType: 'all', adaptation: 'simplify', parameters: { maxOptions: 4, largeText: true } }, { componentType: 'secondary-actions', adaptation: 'hide', parameters: {} }, { componentType: 'therapeutic', adaptation: 'emphasize', parameters: { showCalmingColors: true } }); break; case 'mild': adaptations.push({ componentType: 'all', adaptation: 'simplify', parameters: { maxOptions: 6 } }, { componentType: 'therapeutic', adaptation: 'emphasize', parameters: { showProgressIndicators: true } }); break; } crisisModeState = { ...crisisModeState, adaptations }; } /** * Check if there are recent crisis triggers */ function hasRecentTriggers() { const recentTime = Date.now() - (5 * 60 * 1000); // 5 minutes ago return crisisModeState.triggers.some(trigger => trigger.timestamp.getTime() > recentTime); } /** * Monitor behavioral patterns for crisis detection */ export function monitorBehavioralPatterns() { let clickCount = 0; let lastClickTime = 0; let errorCount = 0; let taskSwitchCount = 0; // Click monitoring document.addEventListener('click', () => { const now = Date.now(); if (now - lastClickTime < 2000) { clickCount++; if (crisisDetectionPatterns.rapidClicking(clickCount, now - lastClickTime)) { activateCrisisMode({ type: 'behavioral', value: { clickCount, timeWindow: now - lastClickTime }, confidence: 0.7, timestamp: new Date() }, 'mild'); } } else { clickCount = 1; } lastClickTime = now; }); // Error monitoring window.addEventListener('error', () => { errorCount++; const recentErrors = errorCount > 3; if (recentErrors) { activateCrisisMode({ type: 'behavioral', value: { errorCount }, confidence: 0.6, timestamp: new Date() }, 'mild'); } }); // Reset counters periodically setInterval(() => { clickCount = 0; errorCount = 0; taskSwitchCount = 0; }, 60000); // Reset every minute } /** * Get crisis mode adaptations for a specific component type */ export function getAdaptationsForComponent(componentType) { if (!crisisModeState.enabled) return []; return crisisModeState.adaptations.filter(adaptation => adaptation.componentType === componentType || adaptation.componentType === 'all'); } /** * Apply crisis mode styles to component */ export function applyCrisisModeStyles(baseClasses, componentType) { if (!crisisModeState.enabled) return baseClasses; const adaptations = getAdaptationsForComponent(componentType); let classes = baseClasses; adaptations.forEach(adaptation => { switch (adaptation.adaptation) { case 'simplify': classes += ' ck-crisis-simple'; if (adaptation.parameters.largeText) classes += ' ck-crisis-large-text'; if (adaptation.parameters.highContrast) classes += ' ck-crisis-high-contrast'; break; case 'hide': classes += ' ck-crisis-hidden'; break; case 'emphasize': classes += ' ck-crisis-emphasized'; if (adaptation.parameters.showCalmingColors) classes += ' ck-crisis-calming'; break; } }); return classes; } /** * Check if a component should be shown in crisis mode */ export function shouldShowInCrisisMode(componentType, importance = 'optional') { if (!crisisModeState.enabled) return true; const adaptations = getAdaptationsForComponent(componentType); const hideAdaptation = adaptations.find(a => a.adaptation === 'hide'); if (hideAdaptation) return false; // Severity-based filtering switch (crisisModeState.severity) { case 'severe': return importance === 'essential'; case 'moderate': return importance === 'essential' || importance === 'important'; case 'mild': default: return true; } } /** * Get simplified options for crisis mode */ export function getSimplifiedOptions(options, componentType) { if (!crisisModeState.enabled) return options; const adaptations = getAdaptationsForComponent(componentType); const simplifyAdaptation = adaptations.find(a => a.adaptation === 'simplify'); if (simplifyAdaptation && simplifyAdaptation.parameters.maxOptions) { return options.slice(0, simplifyAdaptation.parameters.maxOptions); } return options; } /** * Manual crisis mode toggle for user control */ export function toggleCrisisMode() { if (crisisModeState.enabled) { deactivateCrisisMode('user'); } else { activateCrisisMode({ type: 'user-reported', value: 'manual-activation', confidence: 1.0, timestamp: new Date() }, 'moderate'); } } /** * Report user crisis state */ export function reportCrisisState(severity, userInput) { activateCrisisMode({ type: 'user-reported', value: { severity, userInput }, confidence: 1.0, timestamp: new Date() }, severity); } /** * Accessibility announcement helper */ function announceToScreenReader(message) { const announcement = document.createElement('div'); announcement.setAttribute('aria-live', 'assertive'); announcement.setAttribute('aria-atomic', 'true'); announcement.className = 'sr-only'; announcement.textContent = message; document.body.appendChild(announcement); setTimeout(() => document.body.removeChild(announcement), 1000); } /** * Crisis mode event dispatcher */ function dispatchCrisisModeEvent(type, detail) { const event = new CustomEvent(`crisismode:${type}`, { detail }); window.dispatchEvent(event); } /** * Initialize crisis mode monitoring */ export function initializeCrisisMode(config) { if (config) { updateCrisisConfig(config); } if (crisisConfig.autoDetect) { monitorBehavioralPatterns(); } // Add crisis mode toggle keyboard shortcut (Ctrl/Cmd + Shift + C) document.addEventListener('keydown', (event) => { if ((event.ctrlKey || event.metaKey) && event.shiftKey && event.code === 'KeyC') { event.preventDefault(); toggleCrisisMode(); } }); } /** * Crisis mode CSS classes that should be added to global styles */ export const crisisModeCSSClasses = ` .ck-crisis-simple { --ck-space: 1.5; --ck-text-scale: 1.2; --ck-shadow: none; --ck-radius: 0.25rem; } .ck-crisis-large-text { --ck-text-scale: 1.4; font-weight: var(--ck-font-medium); } .ck-crisis-high-contrast { --ck-bg-primary: #ffffff; --ck-bg-secondary: #f8f9fa; --ck-text-primary: #000000; --ck-border: #333333; } .ck-crisis-hidden { display: none !important; } .ck-crisis-emphasized { border: 2px solid var(--ck-accent); background: color-mix(in srgb, var(--ck-accent) 10%, transparent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--ck-accent) 20%, transparent); } .ck-crisis-calming { --ck-accent: #4ecdc4; --ck-success: #45b7aa; --ck-bg-secondary: color-mix(in srgb, #4ecdc4 5%, transparent); } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } `;