# ๐Ÿ“ฑ RESPONSIVE - Documentazione Completa NetGescon > **Sistema Responsive dell'interfaccia universale** > **๐Ÿ“… Creato:** 21/07/2025 > **๐Ÿ”„ Ultimo aggiornamento:** 21/07/2025 > **๐ŸŽฏ Prioritร :** โšก ALTO --- ## ๐Ÿ“‹ **OVERVIEW RESPONSIVE SYSTEM** Il Sistema Responsive NetGescon garantisce **ESPERIENZA OTTIMALE** su: - ๐Ÿ“ฑ **Mobile Devices** (320px - 767px) - ๐Ÿ“Ÿ **Tablet Devices** (768px - 1023px) - ๐Ÿ’ป **Desktop Screens** (1024px - 1439px) - ๐Ÿ–ฅ๏ธ **Large Displays** (1440px+) - ๐Ÿ“บ **Ultra-wide Monitors** (2560px+) ### ๐ŸŽฏ **Responsive Philosophy** - **Mobile First** - Design ottimizzato prima per mobile - **Progressive Enhancement** - Funzionalitร  aggiuntive su schermi piรน grandi - **Fluid Layouts** - Adattamento fluido a qualsiasi dimensione - **Touch Friendly** - Interfaccia ottimizzata per touch --- ## ๐Ÿ“ **BREAKPOINT SYSTEM** ### ๐ŸŽจ **CSS Breakpoints** ```css /* /public/css/responsive/breakpoints.css */ :root { /* === BREAKPOINT DEFINITIONS === */ --netgescon-breakpoint-xs: 320px; /* Extra Small devices */ --netgescon-breakpoint-sm: 640px; /* Small devices */ --netgescon-breakpoint-md: 768px; /* Medium devices */ --netgescon-breakpoint-lg: 1024px; /* Large devices */ --netgescon-breakpoint-xl: 1280px; /* Extra Large devices */ --netgescon-breakpoint-2xl: 1536px; /* 2X Large devices */ --netgescon-breakpoint-3xl: 1920px; /* 3X Large devices */ } /* === MEDIA QUERY MIXINS === */ @media (max-width: 639px) { /* Mobile styles */ .mobile-only { display: block !important; } .tablet-up { display: none !important; } .desktop-up { display: none !important; } } @media (min-width: 640px) and (max-width: 767px) { /* Small tablet styles */ .mobile-only { display: none !important; } .tablet-only { display: block !important; } } @media (min-width: 768px) { /* Tablet and up */ .mobile-only { display: none !important; } .tablet-up { display: block !important; } } @media (min-width: 1024px) { /* Desktop and up */ .tablet-only { display: none !important; } .desktop-up { display: block !important; } } @media (min-width: 1280px) { /* Large desktop */ .desktop-only { display: none !important; } .large-desktop-up { display: block !important; } } ``` ### ๐Ÿ“ฑ **JavaScript Breakpoints** ```javascript // /public/js/responsive/breakpoints.js class ResponsiveManager { constructor() { this.breakpoints = { xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280, '2xl': 1536, '3xl': 1920 }; this.currentBreakpoint = this.getCurrentBreakpoint(); this.orientation = this.getOrientation(); this.init(); } init() { // Listen for resize events window.addEventListener('resize', this.handleResize.bind(this)); // Listen for orientation change window.addEventListener('orientationchange', this.handleOrientationChange.bind(this)); // Initial setup this.updateLayout(); } getCurrentBreakpoint() { const width = window.innerWidth; if (width >= this.breakpoints['3xl']) return '3xl'; if (width >= this.breakpoints['2xl']) return '2xl'; if (width >= this.breakpoints.xl) return 'xl'; if (width >= this.breakpoints.lg) return 'lg'; if (width >= this.breakpoints.md) return 'md'; if (width >= this.breakpoints.sm) return 'sm'; return 'xs'; } getOrientation() { return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape'; } handleResize() { const newBreakpoint = this.getCurrentBreakpoint(); const newOrientation = this.getOrientation(); if (newBreakpoint !== this.currentBreakpoint || newOrientation !== this.orientation) { this.currentBreakpoint = newBreakpoint; this.orientation = newOrientation; this.updateLayout(); // Dispatch custom event document.dispatchEvent(new CustomEvent('netgescon:breakpoint-change', { detail: { breakpoint: this.currentBreakpoint, orientation: this.orientation, width: window.innerWidth, height: window.innerHeight } })); } } handleOrientationChange() { setTimeout(() => { this.handleResize(); }, 100); } updateLayout() { document.body.className = document.body.className .replace(/breakpoint-\w+/g, '') .replace(/orientation-\w+/g, ''); document.body.classList.add(`breakpoint-${this.currentBreakpoint}`); document.body.classList.add(`orientation-${this.orientation}`); // Update CSS custom property document.documentElement.style.setProperty('--current-breakpoint', this.currentBreakpoint); } isMobile() { return ['xs', 'sm'].includes(this.currentBreakpoint); } isTablet() { return this.currentBreakpoint === 'md'; } isDesktop() { return ['lg', 'xl', '2xl', '3xl'].includes(this.currentBreakpoint); } isTouch() { return 'ontouchstart' in window || navigator.maxTouchPoints > 0; } } // Initialize responsive manager const ResponsiveManager = new ResponsiveManager(); window.ResponsiveManager = ResponsiveManager; ``` --- ## ๐Ÿ—๏ธ **LAYOUT SYSTEM** ### ๐Ÿ“ฑ **Mobile Layout (320px - 767px)** ```css /* /public/css/responsive/mobile.css */ @media (max-width: 767px) { /* === LAYOUT STRUCTURE === */ .netgescon-layout { display: flex; flex-direction: column; min-height: 100vh; } /* Header mobile */ .netgescon-header { position: fixed; top: 0; left: 0; right: 0; height: 60px; z-index: 1000; padding: 0 1rem; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } /* Sidebar mobile */ .netgescon-sidebar { position: fixed; top: 60px; left: -280px; width: 280px; height: calc(100vh - 60px); background: white; box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15); transition: left 0.3s ease; z-index: 999; overflow-y: auto; } .netgescon-sidebar.show { left: 0; } /* Content mobile */ .netgescon-content { margin-top: 60px; padding: 1rem; flex: 1; min-height: calc(100vh - 60px); } /* Footer mobile */ .netgescon-footer { display: none; /* Hidden on mobile */ } /* === OVERLAY === */ .sidebar-overlay { position: fixed; top: 60px; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); opacity: 0; visibility: hidden; transition: all 0.3s ease; z-index: 998; } .sidebar-overlay.show { opacity: 1; visibility: visible; } /* === MOBILE NAVIGATION === */ .mobile-nav-toggle { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; background: none; border: none; color: var(--netgescon-gray-600); font-size: 1.25rem; cursor: pointer; } .mobile-nav-toggle:hover { color: var(--netgescon-primary); background: var(--netgescon-gray-100); border-radius: var(--netgescon-border-radius); } /* === MOBILE CARDS === */ .netgescon-card { margin-bottom: 1rem; padding: 1rem; } .stats-grid { grid-template-columns: 1fr; gap: 1rem; } .content-grid { grid-template-columns: 1fr; gap: 1.5rem; } /* === MOBILE FORMS === */ .form-row { flex-direction: column; } .form-group { margin-bottom: 1rem; } .btn-group { flex-direction: column; gap: 0.5rem; } .btn-group .btn { width: 100%; justify-content: center; } /* === MOBILE TABLES === */ .table-responsive-mobile { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .mobile-table-stack { display: none; } @media (max-width: 576px) { .table-responsive-mobile table { display: none; } .mobile-table-stack { display: block; } .mobile-table-item { background: white; border: 1px solid var(--netgescon-gray-200); border-radius: var(--netgescon-border-radius); padding: 1rem; margin-bottom: 1rem; } .mobile-table-item .item-header { font-weight: var(--netgescon-font-semibold); color: var(--netgescon-gray-900); margin-bottom: 0.5rem; } .mobile-table-item .item-row { display: flex; justify-content: space-between; align-items: center; padding: 0.25rem 0; border-bottom: 1px solid var(--netgescon-gray-100); } .mobile-table-item .item-row:last-child { border-bottom: none; } .mobile-table-item .item-label { font-weight: var(--netgescon-font-medium); color: var(--netgescon-gray-600); font-size: var(--netgescon-text-sm); } .mobile-table-item .item-value { color: var(--netgescon-gray-900); text-align: right; } } } ``` ### ๐Ÿ“Ÿ **Tablet Layout (768px - 1023px)** ```css /* /public/css/responsive/tablet.css */ @media (min-width: 768px) and (max-width: 1023px) { /* === LAYOUT STRUCTURE === */ .netgescon-layout { display: grid; grid-template-areas: "header header" "sidebar content" "sidebar footer"; grid-template-columns: 260px 1fr; grid-template-rows: 70px 1fr auto; min-height: 100vh; } /* Header tablet */ .netgescon-header { grid-area: header; position: sticky; top: 0; z-index: 100; } /* Sidebar tablet */ .netgescon-sidebar { grid-area: sidebar; width: 260px; position: sticky; top: 70px; height: calc(100vh - 70px); overflow-y: auto; border-right: 1px solid var(--netgescon-gray-200); } .netgescon-sidebar.collapsed { width: 70px; grid-template-columns: 70px 1fr; } /* Content tablet */ .netgescon-content { grid-area: content; padding: 1.5rem; overflow-x: auto; } /* Footer tablet */ .netgescon-footer { grid-area: footer; border-top: 1px solid var(--netgescon-gray-200); } /* === TABLET CARDS === */ .stats-grid { grid-template-columns: repeat(2, 1fr); gap: 1.5rem; } .content-grid { grid-template-columns: repeat(2, 1fr); gap: 2rem; } /* === TABLET FORMS === */ .form-row { display: flex; gap: 1rem; } .form-row .form-group { flex: 1; } .btn-group { flex-direction: row; gap: 1rem; } /* === TABLET MODALS === */ .modal-dialog { max-width: 600px; margin: 2rem auto; } .modal-dialog-lg { max-width: 800px; } } ``` ### ๐Ÿ’ป **Desktop Layout (1024px+)** ```css /* /public/css/responsive/desktop.css */ @media (min-width: 1024px) { /* === LAYOUT STRUCTURE === */ .netgescon-layout { display: grid; grid-template-areas: "header header" "sidebar content" "sidebar footer"; grid-template-columns: 280px 1fr; grid-template-rows: 70px 1fr 70px; min-height: 100vh; } /* Header desktop */ .netgescon-header { grid-area: header; position: sticky; top: 0; z-index: 100; } /* Sidebar desktop */ .netgescon-sidebar { grid-area: sidebar; width: 280px; position: sticky; top: 70px; height: calc(100vh - 140px); overflow-y: auto; border-right: 1px solid var(--netgescon-gray-200); } .netgescon-sidebar.collapsed { width: 70px; .sidebar-text { display: none; } .sidebar-icon { margin: 0 auto; } } /* Content desktop */ .netgescon-content { grid-area: content; padding: 2rem; overflow-x: auto; } /* Footer desktop */ .netgescon-footer { grid-area: footer; height: 70px; border-top: 1px solid var(--netgescon-gray-200); } /* === DESKTOP CARDS === */ .stats-grid { grid-template-columns: repeat(4, 1fr); gap: 2rem; } .content-grid { grid-template-columns: repeat(3, 1fr); gap: 2rem; } /* === DESKTOP FORMS === */ .form-row { display: flex; gap: 1.5rem; } .form-row .form-group { flex: 1; } .form-row .form-group.col-2 { flex: 2; } .form-row .form-group.col-3 { flex: 3; } /* === DESKTOP MODALS === */ .modal-dialog { max-width: 800px; margin: 3rem auto; } .modal-dialog-lg { max-width: 1000px; } .modal-dialog-xl { max-width: 1200px; } /* === DESKTOP TOOLTIPS === */ .tooltip { display: block; } /* === DESKTOP HOVER EFFECTS === */ .netgescon-card:hover { transform: translateY(-2px); box-shadow: var(--netgescon-shadow-lg); } .btn:hover { transform: translateY(-1px); box-shadow: var(--netgescon-shadow-md); } } ``` --- ## ๐Ÿ“ฑ **MOBILE ENHANCEMENTS** ### ๐Ÿ‘† **Touch Interactions** ```css /* /public/css/responsive/touch.css */ /* === TOUCH OPTIMIZATIONS === */ @media (hover: none) and (pointer: coarse) { /* Touch device specific styles */ /* Larger touch targets */ .btn { min-height: 44px; min-width: 44px; padding: 12px 16px; } .form-input { min-height: 44px; padding: 12px; font-size: 16px; /* Prevent zoom on iOS */ } .form-select { min-height: 44px; padding: 12px; font-size: 16px; } /* Touch-friendly navigation */ .nav-link { padding: 12px 16px; min-height: 44px; display: flex; align-items: center; } /* Touch-friendly table rows */ .table tbody tr { min-height: 48px; } .table tbody td { padding: 12px 8px; } /* Remove hover effects on touch */ .netgescon-card:hover { transform: none; box-shadow: var(--netgescon-shadow); } .btn:hover { transform: none; box-shadow: none; } /* Touch feedback */ .btn:active { transform: scale(0.98); background-color: var(--netgescon-primary-dark); } .netgescon-card:active { transform: scale(0.99); } } ``` ### ๐Ÿ“ฑ **Mobile Navigation** ```javascript // /public/js/responsive/mobile-nav.js class MobileNavigation { constructor() { this.sidebar = document.getElementById('sidebar'); this.overlay = document.getElementById('sidebar-overlay'); this.toggleBtn = document.getElementById('mobile-nav-toggle'); this.isOpen = false; this.init(); } init() { if (!this.sidebar || !this.toggleBtn) return; // Create overlay if it doesn't exist if (!this.overlay) { this.createOverlay(); } // Bind events this.toggleBtn.addEventListener('click', this.toggle.bind(this)); this.overlay.addEventListener('click', this.close.bind(this)); // Close on escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen) { this.close(); } }); // Close on orientation change window.addEventListener('orientationchange', () => { setTimeout(() => this.close(), 100); }); // Close when switching to desktop window.addEventListener('resize', () => { if (window.innerWidth >= 768 && this.isOpen) { this.close(); } }); } createOverlay() { this.overlay = document.createElement('div'); this.overlay.id = 'sidebar-overlay'; this.overlay.className = 'sidebar-overlay'; document.body.appendChild(this.overlay); } toggle() { if (this.isOpen) { this.close(); } else { this.open(); } } open() { this.isOpen = true; this.sidebar.classList.add('show'); this.overlay.classList.add('show'); document.body.style.overflow = 'hidden'; // Trap focus in sidebar this.trapFocus(); // Dispatch event document.dispatchEvent(new CustomEvent('netgescon:mobile-nav:open')); } close() { this.isOpen = false; this.sidebar.classList.remove('show'); this.overlay.classList.remove('show'); document.body.style.overflow = ''; // Release focus trap this.releaseFocus(); // Dispatch event document.dispatchEvent(new CustomEvent('netgescon:mobile-nav:close')); } trapFocus() { const focusableElements = this.sidebar.querySelectorAll( 'a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select' ); if (focusableElements.length === 0) return; const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1]; this.focusHandler = (e) => { if (e.key === 'Tab') { if (e.shiftKey) { if (document.activeElement === firstElement) { e.preventDefault(); lastElement.focus(); } } else { if (document.activeElement === lastElement) { e.preventDefault(); firstElement.focus(); } } } }; document.addEventListener('keydown', this.focusHandler); firstElement.focus(); } releaseFocus() { if (this.focusHandler) { document.removeEventListener('keydown', this.focusHandler); this.focusHandler = null; } } } // Initialize mobile navigation document.addEventListener('DOMContentLoaded', () => { if (window.innerWidth < 768) { new MobileNavigation(); } }); ``` --- ## ๐Ÿ“Š **RESPONSIVE TABLES** ### ๐Ÿ“ฑ **Mobile Table Stack** ```javascript // /public/js/responsive/responsive-tables.js class ResponsiveTables { constructor() { this.tables = document.querySelectorAll('.table-responsive-mobile'); this.init(); } init() { this.tables.forEach(tableContainer => { this.enhanceTable(tableContainer); }); // Re-initialize on breakpoint change document.addEventListener('netgescon:breakpoint-change', (e) => { if (e.detail.breakpoint === 'xs') { this.createMobileViews(); } else if (e.detail.breakpoint !== 'xs') { this.removeMobileViews(); } }); } enhanceTable(container) { const table = container.querySelector('table'); if (!table) return; // Create mobile stack version this.createMobileStack(container, table); // Add horizontal scroll indicators this.addScrollIndicators(container); } createMobileStack(container, table) { const mobileStack = document.createElement('div'); mobileStack.className = 'mobile-table-stack'; const headers = Array.from(table.querySelectorAll('thead th')).map(th => th.textContent.trim()); const rows = table.querySelectorAll('tbody tr'); rows.forEach(row => { const cells = row.querySelectorAll('td'); const mobileItem = document.createElement('div'); mobileItem.className = 'mobile-table-item'; // Create header if there's a primary column const primaryCell = cells[0]; if (primaryCell) { const itemHeader = document.createElement('div'); itemHeader.className = 'item-header'; itemHeader.textContent = primaryCell.textContent.trim(); mobileItem.appendChild(itemHeader); } // Create rows for each cell cells.forEach((cell, index) => { if (index === 0) return; // Skip primary cell const itemRow = document.createElement('div'); itemRow.className = 'item-row'; const label = document.createElement('div'); label.className = 'item-label'; label.textContent = headers[index] || `Campo ${index}`; const value = document.createElement('div'); value.className = 'item-value'; value.innerHTML = cell.innerHTML; itemRow.appendChild(label); itemRow.appendChild(value); mobileItem.appendChild(itemRow); }); mobileStack.appendChild(mobileItem); }); container.appendChild(mobileStack); } addScrollIndicators(container) { const table = container.querySelector('table'); if (!table) return; const indicator = document.createElement('div'); indicator.className = 'scroll-indicator'; indicator.innerHTML = ' Scorri per vedere piรน colonne'; container.appendChild(indicator); // Hide indicator when scrolled to end container.addEventListener('scroll', () => { const isScrolledToEnd = container.scrollLeft >= (container.scrollWidth - container.clientWidth - 10); indicator.style.opacity = isScrolledToEnd ? '0' : '1'; }); } createMobileViews() { this.tables.forEach(container => { container.classList.add('mobile-view'); }); } removeMobileViews() { this.tables.forEach(container => { container.classList.remove('mobile-view'); }); } } // Initialize responsive tables document.addEventListener('DOMContentLoaded', () => { new ResponsiveTables(); }); ``` --- ## ๐ŸŽจ **RESPONSIVE COMPONENTS** ### ๐Ÿ“ฑ **Responsive Cards** ```blade @props([ 'title' => '', 'mobileTitle' => null, 'content' => '', 'actions' => [], 'mobileLayout' => 'stack' ])

{{ $title }}

@if(count($actions) > 0)
@foreach($actions as $action) @if(isset($action['icon'])) @endif {{ $action['text'] }} @endforeach
@endif

{{ $mobileTitle ?? $title }}

@if(count($actions) > 0) @endif
{!! $content !!}
``` ### ๐Ÿ“ฑ **Responsive Form** ```blade @props([ 'action' => '', 'method' => 'POST', 'columns' => 1, 'mobileColumns' => 1 ])
@csrf @if($method !== 'GET' && $method !== 'POST') @method($method) @endif
{{ $slot }}
{{ $actions ?? '' }}
``` --- ## ๐Ÿงช **TESTING RESPONSIVO** ### โœ… **Device Testing Checklist** ```javascript // /public/js/testing/responsive-tests.js class ResponsiveTests { constructor() { this.testResults = new Map(); this.deviceSizes = { 'iPhone SE': { width: 375, height: 667 }, 'iPhone 12': { width: 390, height: 844 }, 'iPad': { width: 768, height: 1024 }, 'iPad Pro': { width: 1024, height: 1366 }, 'Desktop HD': { width: 1920, height: 1080 }, 'Desktop 4K': { width: 3840, height: 2160 } }; this.runTests(); } runTests() { console.log('๐Ÿงช Running Responsive Tests...'); Object.entries(this.deviceSizes).forEach(([device, size]) => { this.testDevice(device, size); }); this.generateReport(); } testDevice(deviceName, size) { console.log(`๐Ÿ“ฑ Testing ${deviceName} (${size.width}x${size.height})`); // Simulate device size this.setViewportSize(size.width, size.height); const tests = [ () => this.testLayout(), () => this.testNavigation(), () => this.testForms(), () => this.testTables(), () => this.testCards(), () => this.testButtons(), () => this.testImages(), () => this.testText() ]; const results = tests.map(test => { try { return test(); } catch (error) { return { passed: false, error: error.message }; } }); this.testResults.set(deviceName, results); } setViewportSize(width, height) { // This would work in a testing environment // In browser, we can only simulate with CSS document.documentElement.style.setProperty('--test-width', `${width}px`); document.documentElement.style.setProperty('--test-height', `${height}px`); } testLayout() { const layout = document.querySelector('.netgescon-layout'); const sidebar = document.querySelector('.netgescon-sidebar'); const content = document.querySelector('.netgescon-content'); const tests = [ layout !== null, sidebar !== null, content !== null, !this.hasHorizontalScrollbar(document.body), this.isContentVisible(content) ]; return { name: 'Layout', passed: tests.every(Boolean), details: { hasLayout: tests[0], hasSidebar: tests[1], hasContent: tests[2], noHorizontalScroll: tests[3], contentVisible: tests[4] } }; } testNavigation() { const navItems = document.querySelectorAll('.nav-link'); const mobileToggle = document.querySelector('.mobile-nav-toggle'); const tests = [ navItems.length > 0, window.innerWidth < 768 ? mobileToggle !== null : true, this.areElementsTouchFriendly(navItems) ]; return { name: 'Navigation', passed: tests.every(Boolean), details: { hasNavItems: tests[0], hasMobileToggle: tests[1], touchFriendly: tests[2] } }; } testForms() { const inputs = document.querySelectorAll('.form-input'); const buttons = document.querySelectorAll('.btn'); const tests = [ this.areElementsTouchFriendly(inputs), this.areElementsTouchFriendly(buttons), this.hasProperFontSize(inputs) ]; return { name: 'Forms', passed: tests.every(Boolean), details: { inputsTouchFriendly: tests[0], buttonsTouchFriendly: tests[1], properFontSize: tests[2] } }; } testTables() { const tables = document.querySelectorAll('.table-responsive-mobile'); if (tables.length === 0) { return { name: 'Tables', passed: true, details: { noTables: true } }; } const tests = [ !this.hasHorizontalScrollbar(tables[0]), window.innerWidth < 576 ? this.hasMobileTableVersion(tables[0]) : true ]; return { name: 'Tables', passed: tests.every(Boolean), details: { noHorizontalScroll: tests[0], hasMobileVersion: tests[1] } }; } testCards() { const cards = document.querySelectorAll('.netgescon-card'); const tests = [ cards.length > 0, this.areCardsResponsive(cards) ]; return { name: 'Cards', passed: tests.every(Boolean), details: { hasCards: tests[0], responsive: tests[1] } }; } testButtons() { const buttons = document.querySelectorAll('.btn'); const tests = [ this.areElementsTouchFriendly(buttons), this.haveProperSpacing(buttons) ]; return { name: 'Buttons', passed: tests.every(Boolean), details: { touchFriendly: tests[0], properSpacing: tests[1] } }; } testImages() { const images = document.querySelectorAll('img'); const tests = [ this.areImagesResponsive(images), this.haveAltText(images) ]; return { name: 'Images', passed: tests.every(Boolean), details: { responsive: tests[0], hasAltText: tests[1] } }; } testText() { const textElements = document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, span'); const tests = [ this.hasReadableSize(textElements), this.hasProperLineHeight(textElements) ]; return { name: 'Text', passed: tests.every(Boolean), details: { readableSize: tests[0], properLineHeight: tests[1] } }; } // Helper methods hasHorizontalScrollbar(element) { return element.scrollWidth > element.clientWidth; } isContentVisible(element) { const rect = element.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; } areElementsTouchFriendly(elements) { return Array.from(elements).every(el => { const rect = el.getBoundingClientRect(); return rect.height >= 44 && rect.width >= 44; }); } hasProperFontSize(elements) { return Array.from(elements).every(el => { const style = getComputedStyle(el); const fontSize = parseFloat(style.fontSize); return fontSize >= 16; // Prevent zoom on iOS }); } hasMobileTableVersion(tableContainer) { return tableContainer.querySelector('.mobile-table-stack') !== null; } areCardsResponsive(cards) { return Array.from(cards).every(card => { const rect = card.getBoundingClientRect(); return rect.width <= window.innerWidth; }); } haveProperSpacing(elements) { // Check if buttons have adequate spacing between them const buttons = Array.from(elements); for (let i = 0; i < buttons.length - 1; i++) { const current = buttons[i].getBoundingClientRect(); const next = buttons[i + 1].getBoundingClientRect(); const spacing = Math.abs(next.left - current.right); if (spacing < 8 && spacing > 0) return false; } return true; } areImagesResponsive(images) { return Array.from(images).every(img => { const style = getComputedStyle(img); return style.maxWidth === '100%' || style.width === '100%'; }); } haveAltText(images) { return Array.from(images).every(img => img.hasAttribute('alt')); } hasReadableSize(elements) { return Array.from(elements).every(el => { const style = getComputedStyle(el); const fontSize = parseFloat(style.fontSize); return fontSize >= 14; }); } hasProperLineHeight(elements) { return Array.from(elements).every(el => { const style = getComputedStyle(el); const lineHeight = parseFloat(style.lineHeight); const fontSize = parseFloat(style.fontSize); return lineHeight / fontSize >= 1.2; }); } generateReport() { console.log('\n๐Ÿ“Š Responsive Test Report'); console.log('========================='); this.testResults.forEach((results, device) => { console.log(`\n๐Ÿ“ฑ ${device}:`); results.forEach(test => { const status = test.passed ? 'โœ…' : 'โŒ'; console.log(` ${status} ${test.name}`); if (!test.passed && test.details) { Object.entries(test.details).forEach(([key, value]) => { if (!value) { console.log(` โš ๏ธ ${key}: failed`); } }); } }); }); // Overall summary const totalTests = Array.from(this.testResults.values()).flat().length; const passedTests = Array.from(this.testResults.values()).flat().filter(t => t.passed).length; const passRate = Math.round((passedTests / totalTests) * 100); console.log(`\n๐Ÿ“ˆ Overall Pass Rate: ${passRate}% (${passedTests}/${totalTests})`); } } // Run tests in development if (NetGesconConfig.get('app.debug')) { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { new ResponsiveTests(); }, 2000); }); } ``` --- ## ๐Ÿ“š **FILES CORRELATI** ### ๐Ÿ“ฑ **Responsive CSS Files** - `/public/css/responsive/` (directory) - `/public/css/responsive/breakpoints.css` - `/public/css/responsive/mobile.css` - `/public/css/responsive/tablet.css` - `/public/css/responsive/desktop.css` - `/public/css/responsive/touch.css` ### โšก **Responsive JavaScript Files** - `/public/js/responsive/` (directory) - `/public/js/responsive/breakpoints.js` - `/public/js/responsive/mobile-nav.js` - `/public/js/responsive/responsive-tables.js` - `/public/js/testing/responsive-tests.js` ### ๐Ÿงฉ **Responsive Components** - `/resources/views/components/responsive-card.blade.php` - `/resources/views/components/responsive-form.blade.php` - `/resources/views/components/responsive-table.blade.php` --- ## ๐Ÿงช **TESTING CHECKLIST** ### โœ… **Mobile Testing (320px - 767px)** - [ ] Navigation funzionale - [ ] Touch targets โ‰ฅ 44px - [ ] No horizontal scroll - [ ] Font size โ‰ฅ 16px - [ ] Tables responsive o stack - [ ] Forms easy to use - [ ] Content leggibile ### โœ… **Tablet Testing (768px - 1023px)** - [ ] Layout a 2 colonne - [ ] Sidebar collassabile - [ ] Cards in griglia 2x2 - [ ] Forms a 2 colonne - [ ] Navigation persistente ### โœ… **Desktop Testing (1024px+)** - [ ] Layout a 3 colonne - [ ] Sidebar fissa - [ ] Cards in griglia 4x1 - [ ] Hover effects - [ ] Tooltips funzionali --- ## ๐ŸŽฏ **NEXT STEPS** ### ๐Ÿ”ฎ **Responsive Future** - [ ] **Container Queries** - Layout basato su container size - [ ] **Advanced Grid** - CSS Grid subgrid support - [ ] **Dynamic Viewports** - Support per viewport dinamici - [ ] **Foldable Devices** - Support per schermi pieghevoli - [ ] **AR/VR Integration** - Responsive per dispositivi immersivi --- *๐Ÿ“ Documento mantenuto da: GitHub Copilot AI Assistant* *๐Ÿ”— Componente: Responsive System NetGescon Interface v2.1.0*