- Add 26 documentation files including test reports, deployment guides, and troubleshooting documentation - Include 3 CSV data files for trainer imports and user registration tracking - Add 43 JavaScript test files covering mobile optimization, Safari compatibility, and E2E testing - Include 18 PHP utility files for debugging, geocoding, and data analysis - Add 12 shell scripts for deployment verification, user management, and database operations - Update .gitignore with whitelist patterns for development files, documentation, and CSV data 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			214 lines
		
	
	
		
			No EOL
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			214 lines
		
	
	
		
			No EOL
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const fs = require('fs');
 | ||
| const path = require('path');
 | ||
| 
 | ||
| class ResponsiveAnalyzer {
 | ||
|     constructor() {
 | ||
|         this.breakpoints = new Map();
 | ||
|         this.responsivePatterns = [];
 | ||
|         this.issues = [];
 | ||
|     }
 | ||
| 
 | ||
|     analyzeResponsiveDesign() {
 | ||
|         console.log('📱 RESPONSIVE DESIGN ANALYSIS');
 | ||
|         console.log('='.repeat(50));
 | ||
| 
 | ||
|         const cssFiles = [
 | ||
|             './assets/css/hvac-common.css',
 | ||
|             './assets/css/hvac-dashboard.css',
 | ||
|             './assets/css/hvac-registration.css',
 | ||
|             './assets/css/hvac-certificates.css',
 | ||
|             './assets/css/hvac-email-attendees.css',
 | ||
|             './assets/css/hvac-event-summary.css',
 | ||
|             './assets/css/hvac-attendee-profile.css',
 | ||
|             './assets/css/hvac-mobile-nav.css'
 | ||
|         ];
 | ||
| 
 | ||
|         cssFiles.forEach(file => {
 | ||
|             if (fs.existsSync(file)) {
 | ||
|                 this.analyzeFile(file);
 | ||
|             }
 | ||
|         });
 | ||
| 
 | ||
|         this.generateResponsiveReport();
 | ||
|     }
 | ||
| 
 | ||
|     analyzeFile(filePath) {
 | ||
|         const content = fs.readFileSync(filePath, 'utf8');
 | ||
|         const fileName = path.basename(filePath);
 | ||
|         
 | ||
|         console.log(`\n🔍 ${fileName}:`);
 | ||
|         
 | ||
|         // Extract media queries
 | ||
|         const mediaQueries = this.extractMediaQueries(content);
 | ||
|         if (mediaQueries.length > 0) {
 | ||
|             console.log(`  📱 ${mediaQueries.length} media queries found`);
 | ||
|             mediaQueries.forEach(mq => {
 | ||
|                 console.log(`    ${mq.condition} - ${mq.rules} rules`);
 | ||
|                 
 | ||
|                 // Track breakpoint usage
 | ||
|                 const breakpoint = mq.condition;
 | ||
|                 if (this.breakpoints.has(breakpoint)) {
 | ||
|                     this.breakpoints.set(breakpoint, this.breakpoints.get(breakpoint) + 1);
 | ||
|                 } else {
 | ||
|                     this.breakpoints.set(breakpoint, 1);
 | ||
|                 }
 | ||
|             });
 | ||
|         } else {
 | ||
|             console.log(`  ⚠️  No media queries`);
 | ||
|             this.issues.push({
 | ||
|                 file: fileName,
 | ||
|                 issue: 'No responsive design',
 | ||
|                 severity: 'medium'
 | ||
|             });
 | ||
|         }
 | ||
| 
 | ||
|         // Check for mobile-first vs desktop-first approach
 | ||
|         const mobileFirst = content.includes('min-width');
 | ||
|         const desktopFirst = content.includes('max-width');
 | ||
|         
 | ||
|         if (mobileFirst && desktopFirst) {
 | ||
|             console.log(`  📱 Mixed approach (both min-width and max-width)`);
 | ||
|         } else if (mobileFirst) {
 | ||
|             console.log(`  📱 Mobile-first approach (min-width)`);
 | ||
|         } else if (desktopFirst) {
 | ||
|             console.log(`  🖥️  Desktop-first approach (max-width)`);
 | ||
|         }
 | ||
| 
 | ||
|         // Check for flexible units
 | ||
|         const flexibleUnits = {
 | ||
|             'rem': (content.match(/\d+(\.\d+)?rem/g) || []).length,
 | ||
|             'em': (content.match(/\d+(\.\d+)?em/g) || []).length,
 | ||
|             '%': (content.match(/\d+(\.\d+)?%/g) || []).length,
 | ||
|             'vw': (content.match(/\d+(\.\d+)?vw/g) || []).length,
 | ||
|             'vh': (content.match(/\d+(\.\d+)?vh/g) || []).length,
 | ||
|             'px': (content.match(/\d+px/g) || []).length
 | ||
|         };
 | ||
| 
 | ||
|         console.log(`  📏 Units usage:`, flexibleUnits);
 | ||
| 
 | ||
|         // Check for responsive patterns
 | ||
|         this.checkResponsivePatterns(content, fileName);
 | ||
|     }
 | ||
| 
 | ||
|     extractMediaQueries(content) {
 | ||
|         const mediaRegex = /@media[^{]*\{([^{}]*\{[^}]*\}[^{}]*)*\}/g;
 | ||
|         const queries = [];
 | ||
|         let match;
 | ||
| 
 | ||
|         while ((match = mediaRegex.exec(content)) !== null) {
 | ||
|             const fullQuery = match[0];
 | ||
|             const condition = fullQuery.match(/@media[^{]*/)[0];
 | ||
|             const body = fullQuery.slice(condition.length);
 | ||
|             const rules = (body.match(/[^{}]+\{[^}]*\}/g) || []).length;
 | ||
| 
 | ||
|             queries.push({
 | ||
|                 condition: condition.trim(),
 | ||
|                 body: body,
 | ||
|                 rules: rules
 | ||
|             });
 | ||
|         }
 | ||
| 
 | ||
|         return queries;
 | ||
|     }
 | ||
| 
 | ||
|     checkResponsivePatterns(content, fileName) {
 | ||
|         const patterns = {
 | ||
|             'Flexible Grid': /display:\s*(grid|flex)/g,
 | ||
|             'Responsive Images': /max-width:\s*100%/g,
 | ||
|             'Fluid Typography': /font-size:\s*calc\(/g,
 | ||
|             'Container Queries': /@container/g,
 | ||
|             'Responsive Spacing': /margin|padding.*clamp|margin|padding.*min|margin|padding.*max/g,
 | ||
|             'Responsive Tables': /overflow-x:\s*auto/g
 | ||
|         };
 | ||
| 
 | ||
|         Object.entries(patterns).forEach(([pattern, regex]) => {
 | ||
|             const matches = content.match(regex);
 | ||
|             if (matches) {
 | ||
|                 console.log(`  ✅ ${pattern}: ${matches.length} instances`);
 | ||
|                 this.responsivePatterns.push({
 | ||
|                     file: fileName,
 | ||
|                     pattern: pattern,
 | ||
|                     count: matches.length
 | ||
|                 });
 | ||
|             }
 | ||
|         });
 | ||
|     }
 | ||
| 
 | ||
|     generateResponsiveReport() {
 | ||
|         console.log('\n' + '='.repeat(80));
 | ||
|         console.log('📋 RESPONSIVE DESIGN REPORT');
 | ||
|         console.log('='.repeat(80));
 | ||
| 
 | ||
|         // Breakpoint analysis
 | ||
|         console.log('\n📱 BREAKPOINT USAGE:');
 | ||
|         const sortedBreakpoints = Array.from(this.breakpoints.entries())
 | ||
|             .sort(([,a], [,b]) => b - a);
 | ||
|         
 | ||
|         sortedBreakpoints.forEach(([breakpoint, count]) => {
 | ||
|             console.log(`  ${breakpoint} - Used ${count} times`);
 | ||
|         });
 | ||
| 
 | ||
|         // Common breakpoints check
 | ||
|         const standardBreakpoints = [
 | ||
|             '(max-width: 768px)',
 | ||
|             '(max-width: 480px)',
 | ||
|             '(max-width: 1024px)',
 | ||
|             '(min-width: 768px)',
 | ||
|             '(min-width: 1024px)'
 | ||
|         ];
 | ||
| 
 | ||
|         console.log('\n📐 BREAKPOINT CONSISTENCY:');
 | ||
|         standardBreakpoints.forEach(bp => {
 | ||
|             if (this.breakpoints.has(bp)) {
 | ||
|                 console.log(`  ✅ ${bp} - Standard breakpoint used`);
 | ||
|             } else {
 | ||
|                 console.log(`  ⚠️  ${bp} - Standard breakpoint not used`);
 | ||
|             }
 | ||
|         });
 | ||
| 
 | ||
|         // Responsive patterns summary
 | ||
|         console.log('\n🎨 RESPONSIVE PATTERNS:');
 | ||
|         const patternSummary = new Map();
 | ||
|         this.responsivePatterns.forEach(p => {
 | ||
|             if (patternSummary.has(p.pattern)) {
 | ||
|                 patternSummary.set(p.pattern, patternSummary.get(p.pattern) + p.count);
 | ||
|             } else {
 | ||
|                 patternSummary.set(p.pattern, p.count);
 | ||
|             }
 | ||
|         });
 | ||
| 
 | ||
|         Array.from(patternSummary.entries()).forEach(([pattern, count]) => {
 | ||
|             console.log(`  ✅ ${pattern}: ${count} total uses across files`);
 | ||
|         });
 | ||
| 
 | ||
|         // Issues summary
 | ||
|         if (this.issues.length > 0) {
 | ||
|             console.log('\n⚠️  RESPONSIVE ISSUES:');
 | ||
|             this.issues.forEach((issue, i) => {
 | ||
|                 console.log(`  ${i + 1}. ${issue.file}: ${issue.issue}`);
 | ||
|             });
 | ||
|         }
 | ||
| 
 | ||
|         // Recommendations
 | ||
|         console.log('\n💡 RESPONSIVE RECOMMENDATIONS:');
 | ||
|         
 | ||
|         if (!this.breakpoints.has('(max-width: 320px)')) {
 | ||
|             console.log('  • Consider adding extra-small device support (320px)');
 | ||
|         }
 | ||
|         
 | ||
|         if (!patternSummary.has('Fluid Typography')) {
 | ||
|             console.log('  • Consider implementing fluid typography with clamp()');
 | ||
|         }
 | ||
|         
 | ||
|         if (!patternSummary.has('Container Queries')) {
 | ||
|             console.log('  • Consider using container queries for component-based responsive design');
 | ||
|         }
 | ||
|         
 | ||
|         console.log('  • Test on actual devices, not just browser resize');
 | ||
|         console.log('  • Use relative units (rem, em, %) for better scalability');
 | ||
|         console.log('  • Implement progressive enhancement approach');
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| const analyzer = new ResponsiveAnalyzer();
 | ||
| analyzer.analyzeResponsiveDesign(); |