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
JavaScript
/**
* 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;
}
`;