Learn to detect and optimize for different device types, screen densities, input methods, and hardware capabilities to create tailored experiences.
Modern web development requires creating experiences that adapt not just to screen size, but to the full spectrum of device capabilities, input methods, and performance characteristics. Device-specific optimizations ensure your responsive designs perform optimally whether users are on high-end desktop computers, mid-range tablets, or entry-level smartphones.
This approach goes beyond traditional responsive design by considering factors like device memory, network speed, battery status, input precision, and processing power. By tailoring experiences to device capabilities, you can provide premium experiences for powerful devices while ensuring accessibility and performance for constrained devices.
In this lesson, you'll master the techniques and APIs needed to detect device characteristics and create adaptive optimizations that enhance user experience across the entire device spectrum.
Modern browsers provide APIs to detect device capabilities, allowing you to adapt experiences based on hardware and environmental constraints.
Hardware Detection APIs:
1// Device Memory API
2function getDeviceCapabilities() {
3 const capabilities = {
4 memory: navigator.deviceMemory || 'unknown',
5 hardwareConcurrency: navigator.hardwareConcurrency || 1,
6 maxTouchPoints: navigator.maxTouchPoints || 0,
7 userAgent: navigator.userAgent
8 };
9
10 return capabilities;
11}
12
13// Network Information API
14function getNetworkInfo() {
15 const connection = navigator.connection ||
16 navigator.mozConnection ||
17 navigator.webkitConnection;
18
19 if (connection) {
20 return {
21 effectiveType: connection.effectiveType, // '4g', '3g', '2g', 'slow-2g'
22 downlink: connection.downlink, // Mbps
23 rtt: connection.rtt, // round trip time in ms
24 saveData: connection.saveData // user's data saver preference
25 };
26 }
27
28 return null;
29}
30
31// Battery API (where supported)
32async function getBatteryInfo() {
33 if ('getBattery' in navigator) {
34 try {
35 const battery = await navigator.getBattery();
36 return {
37 charging: battery.charging,
38 level: battery.level,
39 chargingTime: battery.chargingTime,
40 dischargingTime: battery.dischargingTime
41 };
42 } catch (error) {
43 console.warn('Battery API not available:', error);
44 }
45 }
46 return null;
47}
48
49// Comprehensive device profiling
50async function createDeviceProfile() {
51 const capabilities = getDeviceCapabilities();
52 const network = getNetworkInfo();
53 const battery = await getBatteryInfo();
54
55 // Determine device class based on multiple factors
56 let deviceClass = 'unknown';
57
58 if (capabilities.memory) {
59 if (capabilities.memory >= 8) {
60 deviceClass = 'high-end';
61 } else if (capabilities.memory >= 4) {
62 deviceClass = 'mid-range';
63 } else {
64 deviceClass = 'low-end';
65 }
66 }
67
68 // Adjust based on network conditions
69 if (network && (network.effectiveType === 'slow-2g' || network.effectiveType === '2g')) {
70 deviceClass = deviceClass === 'high-end' ? 'mid-range' : 'low-end';
71 }
72
73 return {
74 capabilities,
75 network,
76 battery,
77 deviceClass,
78 recommendations: getOptimizationRecommendations(deviceClass, network)
79 };
80}
81
82function getOptimizationRecommendations(deviceClass, network) {
83 const recommendations = {
84 imageQuality: 'high',
85 animationComplexity: 'full',
86 preloadStrategy: 'aggressive',
87 cacheStrategy: 'comprehensive'
88 };
89
90 if (deviceClass === 'low-end') {
91 recommendations.imageQuality = 'compressed';
92 recommendations.animationComplexity = 'minimal';
93 recommendations.preloadStrategy = 'critical-only';
94 recommendations.cacheStrategy = 'essential';
95 } else if (deviceClass === 'mid-range') {
96 recommendations.imageQuality = 'balanced';
97 recommendations.animationComplexity = 'moderate';
98 recommendations.preloadStrategy = 'selective';
99 recommendations.cacheStrategy = 'balanced';
100 }
101
102 if (network && network.saveData) {
103 recommendations.imageQuality = 'compressed';
104 recommendations.preloadStrategy = 'critical-only';
105 }
106
107 return recommendations;
108}CSS-Based Device Detection:
1/* High-resolution display detection */
2@media (-webkit-min-device-pixel-ratio: 2),
3 (min-resolution: 192dpi),
4 (min-resolution: 2dppx) {
5 .high-res-image {
6 background-image: url('[email protected]');
7 background-size: cover;
8 }
9}
10
11@media (-webkit-min-device-pixel-ratio: 3),
12 (min-resolution: 288dpi),
13 (min-resolution: 3dppx) {
14 .high-res-image {
15 background-image: url('[email protected]');
16 }
17}
18
19/* Input method detection */
20@media (hover: hover) and (pointer: fine) {
21 /* Mouse/trackpad - precise pointing device with hover */
22 .interactive-element:hover {
23 transform: scale(1.05);
24 transition: transform 0.2s ease;
25 }
26}
27
28@media (hover: none) and (pointer: coarse) {
29 /* Touch device - no hover, coarse pointer */
30 .interactive-element {
31 padding: 12px 16px; /* Larger touch targets */
32 min-height: 48px;
33 }
34
35 .interactive-element:active {
36 transform: scale(0.98);
37 transition: transform 0.1s ease;
38 }
39}
40
41@media (pointer: coarse) and (hover: hover) {
42 /* Stylus or precision touch with hover capability */
43 .drawing-area {
44 touch-action: none; /* Enable stylus drawing */
45 }
46}
47
48/* Motion preference detection */
49@media (prefers-reduced-motion: reduce) {
50 .animated-element {
51 animation: none !important;
52 transition: none !important;
53 }
54
55 .parallax-element {
56 transform: none !important;
57 }
58}
59
60/* Color scheme preference */
61@media (prefers-color-scheme: dark) {
62 .adaptive-theme {
63 background: #1a1a1a;
64 color: #ffffff;
65 }
66}
67
68/* High contrast preference */
69@media (prefers-contrast: high) {
70 .accessible-interface {
71 border: 2px solid currentColor;
72 background: Canvas;
73 color: CanvasText;
74 }
75}Performance Monitoring:
1// Monitor rendering performance
2function monitorPerformance() {
3 if ('PerformanceObserver' in window) {
4 // Monitor layout shifts
5 const observer = new PerformanceObserver((list) => {
6 for (const entry of list.getEntries()) {
7 if (entry.hadRecentInput) continue;
8
9 if (entry.value > 0.1) {
10 console.warn('Large layout shift detected:', entry.value);
11 // Potentially reduce animation complexity
12 document.documentElement.classList.add('reduce-animations');
13 }
14 }
15 });
16
17 observer.observe({ entryTypes: ['layout-shift'] });
18 }
19
20 // Monitor memory usage
21 if ('memory' in performance) {
22 setInterval(() => {
23 const memory = performance.memory;
24 const usedMemory = memory.usedJSHeapSize / memory.totalJSHeapSize;
25
26 if (usedMemory > 0.8) {
27 console.warn('High memory usage detected');
28 // Trigger cleanup or reduce features
29 optimizeForMemoryConstraints();
30 }
31 }, 30000);
32 }
33}
34
35function optimizeForMemoryConstraints() {
36 // Remove non-essential features
37 document.querySelectorAll('.memory-intensive').forEach(element => {
38 element.style.display = 'none';
39 });
40
41 // Clear caches
42 if ('caches' in window) {
43 caches.keys().then(names => {
44 names.forEach(name => {
45 if (name.includes('images') || name.includes('assets')) {
46 caches.delete(name);
47 }
48 });
49 });
50 }
51}Adaptive Resource Loading:
1class AdaptiveResourceLoader {
2 constructor() {
3 this.deviceProfile = null;
4 this.init();
5 }
6
7 async init() {
8 this.deviceProfile = await createDeviceProfile();
9 this.setupAdaptiveLoading();
10 }
11
12 setupAdaptiveLoading() {
13 const { deviceClass, network } = this.deviceProfile;
14
15 // Adaptive image loading
16 this.setupImageLoading(deviceClass, network);
17
18 // Adaptive script loading
19 this.setupScriptLoading(deviceClass);
20
21 // Adaptive caching strategy
22 this.setupCachingStrategy(deviceClass);
23 }
24
25 setupImageLoading(deviceClass, network) {
26 const images = document.querySelectorAll('img[data-src]');
27
28 images.forEach(img => {
29 const baseSrc = img.dataset.src;
30 let optimizedSrc = baseSrc;
31
32 if (deviceClass === 'low-end' || (network && network.saveData)) {
33 // Use compressed images
34 optimizedSrc = baseSrc.replace(/.(jpg|jpeg|png)$/, '_compressed.$1');
35 } else if (window.devicePixelRatio > 2 && deviceClass === 'high-end') {
36 // Use high-resolution images for capable devices
37 optimizedSrc = baseSrc.replace(/.(jpg|jpeg|png)$/, '@3x.$1');
38 } else if (window.devicePixelRatio > 1) {
39 optimizedSrc = baseSrc.replace(/.(jpg|jpeg|png)$/, '@2x.$1');
40 }
41
42 img.src = optimizedSrc;
43 });
44 }
45
46 setupScriptLoading(deviceClass) {
47 const scriptGroups = {
48 essential: ['core.js', 'navigation.js'],
49 enhanced: ['animations.js', 'interactions.js'],
50 premium: ['3d-effects.js', 'advanced-analytics.js']
51 };
52
53 // Always load essential scripts
54 this.loadScripts(scriptGroups.essential);
55
56 if (deviceClass !== 'low-end') {
57 this.loadScripts(scriptGroups.enhanced);
58 }
59
60 if (deviceClass === 'high-end') {
61 // Load premium features after essential content
62 setTimeout(() => {
63 this.loadScripts(scriptGroups.premium);
64 }, 2000);
65 }
66 }
67
68 loadScripts(scripts) {
69 scripts.forEach(scriptPath => {
70 const script = document.createElement('script');
71 script.src = `/js/${scriptPath}`;
72 script.async = true;
73 document.head.appendChild(script);
74 });
75 }
76
77 setupCachingStrategy(deviceClass) {
78 if ('serviceWorker' in navigator && deviceClass !== 'low-end') {
79 navigator.serviceWorker.register('/sw.js').then(registration => {
80 // Send device class to service worker
81 registration.active.postMessage({
82 type: 'DEVICE_CLASS',
83 deviceClass: deviceClass
84 });
85 });
86 }
87 }
88}
89
90// Initialize adaptive loading
91new AdaptiveResourceLoader();This comprehensive approach to device detection enables you to create truly adaptive experiences that respect device constraints while maximizing capabilities.
Different input methods (touch, mouse, stylus, keyboard) require different interaction patterns and interface optimizations.
Touch Interface Optimization:
1/* Touch-specific optimizations */
2@media (pointer: coarse) {
3 /* Larger touch targets */
4 .btn, .interactive-element {
5 min-height: 48px;
6 min-width: 48px;
7 padding: 12px 16px;
8 /* Adequate spacing between elements */
9 margin: 8px;
10 }
11
12 /* Touch feedback */
13 .touchable:active {
14 background-color: rgba(0, 0, 0, 0.1);
15 transform: scale(0.98);
16 transition: all 0.1s ease;
17 }
18
19 /* Prevent text selection on touch */
20 .ui-element {
21 -webkit-user-select: none;
22 -moz-user-select: none;
23 -ms-user-select: none;
24 user-select: none;
25 -webkit-tap-highlight-color: transparent;
26 }
27
28 /* Optimize scrolling */
29 .scrollable-area {
30 -webkit-overflow-scrolling: touch;
31 overscroll-behavior: contain;
32 }
33
34 /* Touch-friendly forms */
35 input, textarea {
36 font-size: 16px; /* Prevents zoom on iOS */
37 padding: 12px 16px;
38 border-radius: 8px;
39 border: 2px solid #ddd;
40 }
41
42 input:focus, textarea:focus {
43 border-color: #007AFF;
44 box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
45 }
46}
47
48/* Mouse/trackpad optimization */
49@media (pointer: fine) and (hover: hover) {
50 .interactive-element {
51 padding: 8px 12px; /* Smaller, precise targets */
52 transition: all 0.2s ease;
53 }
54
55 .interactive-element:hover {
56 transform: translateY(-2px);
57 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
58 }
59
60 /* Cursor feedback */
61 .clickable {
62 cursor: pointer;
63 }
64
65 .draggable {
66 cursor: grab;
67 }
68
69 .draggable:active {
70 cursor: grabbing;
71 }
72
73 /* Tooltip support */
74 .tooltip-trigger:hover .tooltip {
75 opacity: 1;
76 visibility: visible;
77 transition: opacity 0.3s ease, visibility 0.3s ease;
78 }
79}
80
81/* Stylus/precision touch optimization */
82@media (pointer: fine) and (hover: none) {
83 .drawing-area {
84 touch-action: none; /* Enable drawing */
85 }
86
87 .precise-control {
88 padding: 6px 10px; /* Smaller targets for precision */
89 }
90
91 /* Pressure sensitivity support */
92 .pressure-sensitive {
93 will-change: stroke-width;
94 }
95}JavaScript Input Handling:
1class InputMethodManager {
2 constructor() {
3 this.currentInputMethod = this.detectPrimaryInput();
4 this.setupInputHandlers();
5 this.setupAdaptiveUI();
6 }
7
8 detectPrimaryInput() {
9 // Check for touch capability
10 if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
11 return 'touch';
12 }
13
14 // Check for mouse
15 if (matchMedia('(hover: hover) and (pointer: fine)').matches) {
16 return 'mouse';
17 }
18
19 return 'keyboard';
20 }
21
22 setupInputHandlers() {
23 // Universal event handling
24 document.addEventListener('pointerdown', this.handlePointerDown.bind(this));
25 document.addEventListener('pointermove', this.handlePointerMove.bind(this));
26 document.addEventListener('pointerup', this.handlePointerUp.bind(this));
27
28 // Keyboard navigation
29 document.addEventListener('keydown', this.handleKeyDown.bind(this));
30
31 // Input method changes
32 this.detectInputMethodChanges();
33 }
34
35 handlePointerDown(event) {
36 const element = event.target.closest('[data-interactive]');
37 if (!element) return;
38
39 // Adapt feedback based on input method
40 if (event.pointerType === 'touch') {
41 element.classList.add('touch-active');
42 // Add haptic feedback if available
43 if ('vibrate' in navigator) {
44 navigator.vibrate(10);
45 }
46 } else if (event.pointerType === 'pen') {
47 element.classList.add('pen-active');
48 // Handle pressure sensitivity
49 if (event.pressure > 0.5) {
50 element.classList.add('pressure-high');
51 }
52 } else {
53 element.classList.add('mouse-active');
54 }
55 }
56
57 handlePointerMove(event) {
58 if (event.pointerType === 'pen') {
59 // Handle drawing or precise interaction
60 this.handlePenInput(event);
61 } else if (event.pointerType === 'touch') {
62 // Handle touch gestures
63 this.handleTouchGesture(event);
64 }
65 }
66
67 handlePointerUp(event) {
68 // Clean up active states
69 const element = event.target.closest('[data-interactive]');
70 if (element) {
71 element.classList.remove('touch-active', 'pen-active', 'mouse-active', 'pressure-high');
72 }
73 }
74
75 handleKeyDown(event) {
76 // Enhanced keyboard navigation
77 if (event.key === 'Tab') {
78 // Ensure focus visibility for keyboard users
79 document.body.classList.add('keyboard-navigation');
80 } else if (event.key === 'Escape') {
81 // Universal escape behavior
82 this.handleEscape();
83 } else if (event.key === 'Enter' || event.key === ' ') {
84 // Activate focused element
85 const focused = document.activeElement;
86 if (focused && focused.matches('[data-interactive]')) {
87 focused.click();
88 }
89 }
90 }
91
92 detectInputMethodChanges() {
93 // Switch to keyboard mode on Tab
94 document.addEventListener('keydown', (event) => {
95 if (event.key === 'Tab') {
96 document.body.classList.add('keyboard-user');
97 document.body.classList.remove('mouse-user', 'touch-user');
98 }
99 });
100
101 // Switch to mouse mode on mouse movement
102 document.addEventListener('mousemove', () => {
103 document.body.classList.add('mouse-user');
104 document.body.classList.remove('keyboard-user', 'touch-user');
105 });
106
107 // Switch to touch mode on touch
108 document.addEventListener('touchstart', () => {
109 document.body.classList.add('touch-user');
110 document.body.classList.remove('keyboard-user', 'mouse-user');
111 });
112 }
113
114 setupAdaptiveUI() {
115 // Adapt interface based on primary input method
116 if (this.currentInputMethod === 'touch') {
117 document.body.classList.add('touch-primary');
118 this.enableTouchOptimizations();
119 } else if (this.currentInputMethod === 'mouse') {
120 document.body.classList.add('mouse-primary');
121 this.enableMouseOptimizations();
122 } else {
123 document.body.classList.add('keyboard-primary');
124 this.enableKeyboardOptimizations();
125 }
126 }
127
128 enableTouchOptimizations() {
129 // Larger touch targets
130 document.documentElement.style.setProperty('--touch-target-size', '48px');
131
132 // Disable hover effects
133 document.body.classList.add('no-hover');
134
135 // Enable touch scrolling optimizations
136 const scrollables = document.querySelectorAll('[data-scrollable]');
137 scrollables.forEach(element => {
138 element.style.webkitOverflowScrolling = 'touch';
139 element.style.overscrollBehavior = 'contain';
140 });
141 }
142
143 enableMouseOptimizations() {
144 // Smaller, precise targets
145 document.documentElement.style.setProperty('--touch-target-size', '32px');
146
147 // Enable hover effects
148 document.body.classList.remove('no-hover');
149
150 // Add cursor feedback
151 this.addCursorFeedback();
152 }
153
154 enableKeyboardOptimizations() {
155 // Enhanced focus indicators
156 document.body.classList.add('keyboard-focus-enhanced');
157
158 // Skip links for screen readers
159 this.addSkipLinks();
160
161 // Roving tabindex for complex widgets
162 this.setupRovingTabindex();
163 }
164
165 addCursorFeedback() {
166 const interactiveElements = document.querySelectorAll('[data-interactive]');
167 interactiveElements.forEach(element => {
168 const action = element.dataset.action || 'pointer';
169 element.style.cursor = action === 'drag' ? 'grab' : 'pointer';
170 });
171 }
172
173 addSkipLinks() {
174 const skipLink = document.createElement('a');
175 skipLink.href = '#main';
176 skipLink.textContent = 'Skip to main content';
177 skipLink.className = 'skip-link';
178 document.body.insertBefore(skipLink, document.body.firstChild);
179 }
180
181 setupRovingTabindex() {
182 // Implement roving tabindex for widget groups
183 const widgets = document.querySelectorAll('[data-widget]');
184 widgets.forEach(widget => {
185 const items = widget.querySelectorAll('[data-widget-item]');
186 if (items.length > 0) {
187 // Set initial focus
188 items[0].tabIndex = 0;
189 for (let i = 1; i < items.length; i++) {
190 items[i].tabIndex = -1;
191 }
192
193 // Handle arrow key navigation
194 widget.addEventListener('keydown', (event) => {
195 if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) {
196 event.preventDefault();
197 this.handleWidgetNavigation(event, items);
198 }
199 });
200 }
201 });
202 }
203
204 handleWidgetNavigation(event, items) {
205 const currentIndex = Array.from(items).indexOf(document.activeElement);
206 let nextIndex = currentIndex;
207
208 switch (event.key) {
209 case 'ArrowRight':
210 case 'ArrowDown':
211 nextIndex = (currentIndex + 1) % items.length;
212 break;
213 case 'ArrowLeft':
214 case 'ArrowUp':
215 nextIndex = (currentIndex - 1 + items.length) % items.length;
216 break;
217 }
218
219 if (nextIndex !== currentIndex) {
220 items[currentIndex].tabIndex = -1;
221 items[nextIndex].tabIndex = 0;
222 items[nextIndex].focus();
223 }
224 }
225}
226
227// Initialize input method management
228new InputMethodManager();Gesture Recognition:
1class GestureHandler {
2 constructor(element) {
3 this.element = element;
4 this.startPos = null;
5 this.currentPos = null;
6 this.startTime = null;
7
8 this.setupEventListeners();
9 }
10
11 setupEventListeners() {
12 this.element.addEventListener('pointerdown', this.handleStart.bind(this));
13 this.element.addEventListener('pointermove', this.handleMove.bind(this));
14 this.element.addEventListener('pointerup', this.handleEnd.bind(this));
15 this.element.addEventListener('pointercancel', this.handleEnd.bind(this));
16 }
17
18 handleStart(event) {
19 this.startPos = { x: event.clientX, y: event.clientY };
20 this.currentPos = { ...this.startPos };
21 this.startTime = Date.now();
22
23 // Set pointer capture for reliable tracking
24 this.element.setPointerCapture(event.pointerId);
25 }
26
27 handleMove(event) {
28 if (!this.startPos) return;
29
30 this.currentPos = { x: event.clientX, y: event.clientY };
31
32 const deltaX = this.currentPos.x - this.startPos.x;
33 const deltaY = this.currentPos.y - this.startPos.y;
34
35 // Detect swipe direction
36 if (Math.abs(deltaX) > 50 || Math.abs(deltaY) > 50) {
37 const direction = this.getSwipeDirection(deltaX, deltaY);
38 this.dispatchGesture('swipe', { direction, deltaX, deltaY });
39 }
40 }
41
42 handleEnd(event) {
43 if (!this.startPos) return;
44
45 const duration = Date.now() - this.startTime;
46 const distance = Math.sqrt(
47 Math.pow(this.currentPos.x - this.startPos.x, 2) +
48 Math.pow(this.currentPos.y - this.startPos.y, 2)
49 );
50
51 // Detect tap vs long press
52 if (distance < 10) {
53 if (duration < 300) {
54 this.dispatchGesture('tap', { x: this.startPos.x, y: this.startPos.y });
55 } else {
56 this.dispatchGesture('longpress', { x: this.startPos.x, y: this.startPos.y });
57 }
58 }
59
60 this.reset();
61 }
62
63 getSwipeDirection(deltaX, deltaY) {
64 const absX = Math.abs(deltaX);
65 const absY = Math.abs(deltaY);
66
67 if (absX > absY) {
68 return deltaX > 0 ? 'right' : 'left';
69 } else {
70 return deltaY > 0 ? 'down' : 'up';
71 }
72 }
73
74 dispatchGesture(type, data) {
75 const event = new CustomEvent('gesture', {
76 detail: { type, ...data }
77 });
78 this.element.dispatchEvent(event);
79 }
80
81 reset() {
82 this.startPos = null;
83 this.currentPos = null;
84 this.startTime = null;
85 }
86}
87
88// Usage example
89document.querySelectorAll('[data-gestures]').forEach(element => {
90 new GestureHandler(element);
91
92 element.addEventListener('gesture', (event) => {
93 const { type, direction, x, y } = event.detail;
94
95 switch (type) {
96 case 'swipe':
97 console.log(`Swipe ${direction} detected`);
98 // Handle swipe navigation
99 break;
100 case 'tap':
101 console.log(`Tap at (${x}, ${y})`);
102 // Handle tap action
103 break;
104 case 'longpress':
105 console.log(`Long press at (${x}, ${y})`);
106 // Handle context menu or secondary action
107 break;
108 }
109 });
110});This comprehensive input method optimization ensures your interface adapts to how users actually interact with their devices, providing the most natural and efficient experience for each input modality.
High-resolution displays and varying viewport characteristics require specific optimizations to ensure crisp visuals and optimal layouts across all screen types.
High-DPI Display Optimization:
1/* Responsive images for different pixel densities */
2.responsive-image {
3 width: 100%;
4 height: auto;
5}
6
7/* Standard resolution (1x) */
8@media (-webkit-max-device-pixel-ratio: 1),
9 (max-resolution: 96dpi) {
10 .adaptive-bg {
11 background-image: url('image-1x.jpg');
12 }
13}
14
15/* High resolution (2x) */
16@media (-webkit-min-device-pixel-ratio: 2),
17 (min-resolution: 192dpi),
18 (min-resolution: 2dppx) {
19 .adaptive-bg {
20 background-image: url('image-2x.jpg');
21 background-size: 50% 50%; /* Scale back to intended size */
22 }
23}
24
25/* Ultra-high resolution (3x) */
26@media (-webkit-min-device-pixel-ratio: 3),
27 (min-resolution: 288dpi),
28 (min-resolution: 3dppx) {
29 .adaptive-bg {
30 background-image: url('image-3x.jpg');
31 background-size: 33.333% 33.333%;
32 }
33}
34
35/* SVG icons for crisp display at any resolution */
36.icon {
37 width: 24px;
38 height: 24px;
39 background-image: url('icon.svg');
40 background-size: contain;
41 background-repeat: no-repeat;
42}
43
44/* CSS-based icons for ultimate crispness */
45.css-icon-close::before {
46 content: '';
47 position: absolute;
48 width: 20px;
49 height: 2px;
50 background: currentColor;
51 transform: rotate(45deg);
52}
53
54.css-icon-close::after {
55 content: '';
56 position: absolute;
57 width: 20px;
58 height: 2px;
59 background: currentColor;
60 transform: rotate(-45deg);
61}Viewport-Specific Optimizations:
1/* Ultra-wide screens (21:9 and wider) */
2@media (min-aspect-ratio: 21/9) {
3 .content-wrapper {
4 max-width: 1400px; /* Prevent overly wide content */
5 margin: 0 auto;
6 }
7
8 .hero-section {
9 background-position: center center;
10 background-size: cover;
11 }
12}
13
14/* Portrait orientation optimizations */
15@media (orientation: portrait) {
16 .card-grid {
17 grid-template-columns: 1fr;
18 }
19
20 .hero-text {
21 font-size: clamp(1.5rem, 8vw, 3rem);
22 }
23}
24
25/* Landscape orientation optimizations */
26@media (orientation: landscape) and (max-height: 600px) {
27 /* Reduce vertical spacing on short landscape screens */
28 .section-padding {
29 padding-top: 2rem;
30 padding-bottom: 2rem;
31 }
32
33 .hero-section {
34 min-height: 70vh; /* Reduced from 100vh */
35 }
36}
37
38/* Very small screens (watches, small phones) */
39@media (max-width: 280px) {
40 .mobile-optimized {
41 padding: 0.5rem;
42 font-size: 14px;
43 }
44
45 .button {
46 padding: 8px 12px;
47 font-size: 12px;
48 }
49}
50
51/* Large desktop screens */
52@media (min-width: 1920px) {
53 .desktop-enhanced {
54 padding: 4rem 0;
55 }
56
57 .container {
58 max-width: 1600px;
59 }
60
61 .text-scaling {
62 font-size: 1.125rem; /* Slightly larger for better readability */
63 }
64}
65
66/* Ultra-wide desktop optimization */
67@media (min-width: 2560px) {
68 .ultra-wide-layout {
69 display: grid;
70 grid-template-columns: 1fr 1200px 1fr;
71 }
72
73 .main-content {
74 grid-column: 2;
75 }
76
77 .sidebar-content {
78 grid-column: 3;
79 padding-left: 2rem;
80 }
81}JavaScript Viewport Detection:
1class ViewportManager {
2 constructor() {
3 this.viewport = this.getViewportInfo();
4 this.setupViewportMonitoring();
5 this.optimizeForViewport();
6 }
7
8 getViewportInfo() {
9 return {
10 width: window.innerWidth,
11 height: window.innerHeight,
12 devicePixelRatio: window.devicePixelRatio || 1,
13 aspectRatio: window.innerWidth / window.innerHeight,
14 orientation: window.screen.orientation?.type ||
15 (window.innerWidth > window.innerHeight ? 'landscape-primary' : 'portrait-primary'),
16 availableHeight: window.screen.availHeight,
17 colorDepth: window.screen.colorDepth,
18 pixelDepth: window.screen.pixelDepth
19 };
20 }
21
22 setupViewportMonitoring() {
23 // Debounced resize handler
24 let resizeTimer;
25 window.addEventListener('resize', () => {
26 clearTimeout(resizeTimer);
27 resizeTimer = setTimeout(() => {
28 this.handleViewportChange();
29 }, 150);
30 });
31
32 // Orientation change handling
33 if (screen.orientation) {
34 screen.orientation.addEventListener('change', () => {
35 // Wait for viewport to update after orientation change
36 setTimeout(() => {
37 this.handleViewportChange();
38 }, 300);
39 });
40 }
41
42 // Visual viewport API for mobile browsers
43 if (window.visualViewport) {
44 window.visualViewport.addEventListener('resize', () => {
45 this.handleVisualViewportChange();
46 });
47 }
48 }
49
50 handleViewportChange() {
51 const newViewport = this.getViewportInfo();
52 const oldViewport = this.viewport;
53
54 // Check for significant changes
55 const widthChange = Math.abs(newViewport.width - oldViewport.width) > 50;
56 const orientationChange = newViewport.orientation !== oldViewport.orientation;
57
58 if (widthChange || orientationChange) {
59 this.viewport = newViewport;
60 this.optimizeForViewport();
61
62 // Dispatch custom event
63 window.dispatchEvent(new CustomEvent('viewportchange', {
64 detail: { old: oldViewport, new: newViewport }
65 }));
66 }
67 }
68
69 handleVisualViewportChange() {
70 // Handle virtual keyboard appearance/disappearance
71 const visualViewport = window.visualViewport;
72 const layoutViewport = document.documentElement.clientHeight;
73
74 if (visualViewport.height < layoutViewport * 0.75) {
75 // Virtual keyboard likely open
76 document.body.classList.add('keyboard-open');
77 this.optimizeForKeyboard();
78 } else {
79 document.body.classList.remove('keyboard-open');
80 this.restoreFromKeyboard();
81 }
82 }
83
84 optimizeForViewport() {
85 const { width, height, aspectRatio, orientation, devicePixelRatio } = this.viewport;
86
87 // Set CSS custom properties for JavaScript-CSS communication
88 document.documentElement.style.setProperty('--viewport-width', `${width}px`);
89 document.documentElement.style.setProperty('--viewport-height', `${height}px`);
90 document.documentElement.style.setProperty('--device-pixel-ratio', devicePixelRatio);
91 document.documentElement.style.setProperty('--aspect-ratio', aspectRatio);
92
93 // Apply viewport-specific classes
94 document.body.className = document.body.className.replace(/viewport-\w+/g, '');
95
96 if (width < 480) {
97 document.body.classList.add('viewport-xs');
98 } else if (width < 768) {
99 document.body.classList.add('viewport-sm');
100 } else if (width < 1024) {
101 document.body.classList.add('viewport-md');
102 } else if (width < 1440) {
103 document.body.classList.add('viewport-lg');
104 } else {
105 document.body.classList.add('viewport-xl');
106 }
107
108 // Aspect ratio classes
109 if (aspectRatio > 2) {
110 document.body.classList.add('ultra-wide');
111 } else if (aspectRatio < 0.75) {
112 document.body.classList.add('tall-narrow');
113 }
114
115 // High-DPI optimizations
116 if (devicePixelRatio >= 2) {
117 document.body.classList.add('high-dpi');
118 this.loadHighResolutionAssets();
119 }
120
121 // Orientation-specific optimizations
122 if (orientation.includes('landscape') && height < 500) {
123 document.body.classList.add('short-landscape');
124 this.optimizeForShortLandscape();
125 }
126 }
127
128 loadHighResolutionAssets() {
129 // Replace low-resolution images with high-resolution versions
130 const images = document.querySelectorAll('img[data-high-res]');
131 images.forEach(img => {
132 if (this.viewport.devicePixelRatio >= 2) {
133 const highResSrc = img.dataset.highRes;
134 if (highResSrc && img.src !== highResSrc) {
135 // Preload high-res image
136 const preloadImg = new Image();
137 preloadImg.onload = () => {
138 img.src = highResSrc;
139 };
140 preloadImg.src = highResSrc;
141 }
142 }
143 });
144
145 // Update CSS background images
146 const bgElements = document.querySelectorAll('[data-bg-high-res]');
147 bgElements.forEach(element => {
148 const highResBg = element.dataset.bgHighRes;
149 if (highResBg) {
150 element.style.backgroundImage = `url(${highResBg})`;
151 }
152 });
153 }
154
155 optimizeForShortLandscape() {
156 // Reduce header height
157 const header = document.querySelector('header');
158 if (header) {
159 header.style.minHeight = '60px';
160 }
161
162 // Use horizontal navigation
163 const nav = document.querySelector('.main-nav');
164 if (nav) {
165 nav.classList.add('horizontal-nav');
166 }
167
168 // Reduce section spacing
169 document.documentElement.style.setProperty('--section-padding', '2rem 0');
170 }
171
172 optimizeForKeyboard() {
173 // Ensure focused input is visible above virtual keyboard
174 const activeElement = document.activeElement;
175 if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) {
176 setTimeout(() => {
177 activeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
178 }, 300);
179 }
180 }
181
182 restoreFromKeyboard() {
183 // Restore original layout when keyboard closes
184 document.body.style.minHeight = '';
185 }
186
187 // Utility method for responsive breakpoints
188 static getBreakpoint() {
189 const width = window.innerWidth;
190 if (width < 480) return 'xs';
191 if (width < 768) return 'sm';
192 if (width < 1024) return 'md';
193 if (width < 1440) return 'lg';
194 return 'xl';
195 }
196}
197
198// Initialize viewport management
199const viewportManager = new ViewportManager();
200
201// Export for use in other modules
202window.ViewportManager = ViewportManager;Responsive Images with Art Direction:
1<!-- Art direction for different viewport ratios -->
2<picture>
3 <!-- Ultra-wide screens: landscape crop -->
4 <source media="(min-aspect-ratio: 16/9)"
5 srcset="hero-ultrawide.jpg 1x, [email protected] 2x">
6
7 <!-- Standard desktop: balanced crop -->
8 <source media="(min-width: 768px)"
9 srcset="hero-desktop.jpg 1x, [email protected] 2x">
10
11 <!-- Portrait mobile: vertical crop -->
12 <source media="(orientation: portrait)"
13 srcset="hero-mobile.jpg 1x, [email protected] 2x">
14
15 <!-- Fallback -->
16 <img src="hero-fallback.jpg"
17 alt="Hero image showing product features"
18 loading="lazy"
19 decoding="async">
20</picture>
21
22<!-- Density-responsive images -->
23<img srcset="product-320w.jpg 320w,
24 product-480w.jpg 480w,
25 product-640w.jpg 640w,
26 product-960w.jpg 960w,
27 product-1280w.jpg 1280w"
28 sizes="(max-width: 320px) 280px,
29 (max-width: 480px) 440px,
30 (max-width: 768px) 600px,
31 50vw"
32 src="product-640w.jpg"
33 alt="Product showcase"
34 loading="lazy">This comprehensive approach to screen density and viewport optimization ensures your responsive designs look crisp and perform optimally across the full spectrum of display technologies and viewport configurations.
Adapt your content and feature loading based on device performance characteristics to ensure optimal experiences across the full spectrum of device capabilities.
Device Performance Classification:
1class PerformanceProfiler {
2 constructor() {
3 this.profile = null;
4 this.benchmarkResults = {};
5 this.init();
6 }
7
8 async init() {
9 this.profile = await this.createPerformanceProfile();
10 this.setupAdaptiveLoading();
11 this.monitorOngoingPerformance();
12 }
13
14 async createPerformanceProfile() {
15 // Gather hardware information
16 const hardware = this.getHardwareInfo();
17
18 // Run performance benchmarks
19 const benchmarks = await this.runBenchmarks();
20
21 // Analyze network conditions
22 const network = this.getNetworkConditions();
23
24 // Determine device class
25 const deviceClass = this.classifyDevice(hardware, benchmarks, network);
26
27 return {
28 hardware,
29 benchmarks,
30 network,
31 deviceClass,
32 timestamp: Date.now()
33 };
34 }
35
36 getHardwareInfo() {
37 return {
38 memory: navigator.deviceMemory || 'unknown',
39 cores: navigator.hardwareConcurrency || 1,
40 platform: navigator.platform,
41 userAgent: navigator.userAgent,
42 maxTouchPoints: navigator.maxTouchPoints,
43 pixelRatio: window.devicePixelRatio || 1
44 };
45 }
46
47 async runBenchmarks() {
48 const results = {};
49
50 // CPU benchmark
51 results.cpu = await this.benchmarkCPU();
52
53 // Memory benchmark
54 results.memory = this.benchmarkMemory();
55
56 // Rendering benchmark
57 results.rendering = await this.benchmarkRendering();
58
59 // Network benchmark
60 results.network = await this.benchmarkNetwork();
61
62 return results;
63 }
64
65 async benchmarkCPU() {
66 const start = performance.now();
67
68 // CPU-intensive task
69 let result = 0;
70 for (let i = 0; i < 100000; i++) {
71 result += Math.random() * Math.sin(i) * Math.cos(i);
72 }
73
74 const duration = performance.now() - start;
75
76 // Classify CPU performance
77 if (duration < 10) return 'high';
78 if (duration < 50) return 'medium';
79 return 'low';
80 }
81
82 benchmarkMemory() {
83 if (!performance.memory) return 'unknown';
84
85 const { usedJSHeapSize, totalJSHeapSize, jsHeapSizeLimit } = performance.memory;
86 const memoryUsage = usedJSHeapSize / totalJSHeapSize;
87 const availableMemory = jsHeapSizeLimit / (1024 * 1024 * 1024); // GB
88
89 if (availableMemory > 2 && memoryUsage < 0.5) return 'high';
90 if (availableMemory > 1 && memoryUsage < 0.7) return 'medium';
91 return 'low';
92 }
93
94 async benchmarkRendering() {
95 return new Promise(resolve => {
96 const canvas = document.createElement('canvas');
97 canvas.width = 300;
98 canvas.height = 300;
99 const ctx = canvas.getContext('2d');
100
101 const start = performance.now();
102
103 // Rendering benchmark
104 for (let i = 0; i < 1000; i++) {
105 ctx.fillStyle = `hsl(${i % 360}, 50%, 50%)`;
106 ctx.fillRect(Math.random() * 300, Math.random() * 300, 10, 10);
107 }
108
109 // Use requestAnimationFrame to ensure rendering completion
110 requestAnimationFrame(() => {
111 const duration = performance.now() - start;
112
113 if (duration < 20) resolve('high');
114 else if (duration < 100) resolve('medium');
115 else resolve('low');
116 });
117 });
118 }
119
120 async benchmarkNetwork() {
121 const connection = navigator.connection ||
122 navigator.mozConnection ||
123 navigator.webkitConnection;
124
125 if (connection) {
126 const { effectiveType, downlink, rtt } = connection;
127
128 if (effectiveType === '4g' && downlink > 10) return 'high';
129 if (effectiveType === '4g' || effectiveType === '3g') return 'medium';
130 return 'low';
131 }
132
133 // Fallback: small network test
134 try {
135 const start = performance.now();
136 await fetch('/api/ping', { method: 'HEAD' });
137 const rtt = performance.now() - start;
138
139 if (rtt < 100) return 'high';
140 if (rtt < 300) return 'medium';
141 return 'low';
142 } catch {
143 return 'low';
144 }
145 }
146
147 getNetworkConditions() {
148 const connection = navigator.connection ||
149 navigator.mozConnection ||
150 navigator.webkitConnection;
151
152 return {
153 effectiveType: connection?.effectiveType || 'unknown',
154 downlink: connection?.downlink || 'unknown',
155 rtt: connection?.rtt || 'unknown',
156 saveData: connection?.saveData || false
157 };
158 }
159
160 classifyDevice(hardware, benchmarks, network) {
161 const scores = {
162 high: 0,
163 medium: 0,
164 low: 0
165 };
166
167 // Score based on hardware
168 if (hardware.memory >= 8) scores.high++;
169 else if (hardware.memory >= 4) scores.medium++;
170 else scores.low++;
171
172 if (hardware.cores >= 4) scores.high++;
173 else if (hardware.cores >= 2) scores.medium++;
174 else scores.low++;
175
176 // Score based on benchmarks
177 Object.values(benchmarks).forEach(score => {
178 if (score === 'high') scores.high++;
179 else if (score === 'medium') scores.medium++;
180 else scores.low++;
181 });
182
183 // Network impact
184 if (network.effectiveType === '4g' && !network.saveData) {
185 scores.high++;
186 } else if (network.effectiveType === '3g') {
187 scores.medium++;
188 } else {
189 scores.low++;
190 }
191
192 // Determine classification
193 const maxScore = Math.max(scores.high, scores.medium, scores.low);
194
195 if (scores.high === maxScore) return 'high-end';
196 if (scores.medium === maxScore) return 'mid-range';
197 return 'low-end';
198 }
199
200 setupAdaptiveLoading() {
201 const { deviceClass, network } = this.profile;
202
203 // Configure loading strategies
204 this.setupImageLoading(deviceClass, network);
205 this.setupScriptLoading(deviceClass);
206 this.setupFeatureLoading(deviceClass);
207 this.setupCachingStrategy(deviceClass);
208 }
209
210 setupImageLoading(deviceClass, network) {
211 const imageQuality = this.getImageQuality(deviceClass, network);
212 const loadingStrategy = this.getLoadingStrategy(deviceClass);
213
214 // Apply to existing images
215 document.querySelectorAll('img[data-adaptive]').forEach(img => {
216 this.adaptImage(img, imageQuality, loadingStrategy);
217 });
218
219 // Observer for future images
220 if ('IntersectionObserver' in window && loadingStrategy === 'lazy') {
221 this.setupLazyLoading();
222 }
223 }
224
225 getImageQuality(deviceClass, network) {
226 if (network.saveData || deviceClass === 'low-end') return 'compressed';
227 if (deviceClass === 'mid-range') return 'balanced';
228 return 'high';
229 }
230
231 getLoadingStrategy(deviceClass) {
232 if (deviceClass === 'low-end') return 'critical-only';
233 if (deviceClass === 'mid-range') return 'lazy';
234 return 'progressive';
235 }
236
237 adaptImage(img, quality, strategy) {
238 const baseSrc = img.dataset.src || img.src;
239 const qualitySuffix = quality === 'compressed' ? '_q60' :
240 quality === 'balanced' ? '_q80' : '';
241
242 const adaptedSrc = baseSrc.replace(/\.(jpg|jpeg|png)$/, `${qualitySuffix}.$1`);
243
244 if (strategy === 'critical-only') {
245 // Only load if above the fold
246 const rect = img.getBoundingClientRect();
247 if (rect.top < window.innerHeight) {
248 img.src = adaptedSrc;
249 }
250 } else {
251 img.src = adaptedSrc;
252 }
253 }
254
255 setupScriptLoading(deviceClass) {
256 const scriptGroups = {
257 essential: ['core.js', 'navigation.js'],
258 enhanced: ['interactions.js', 'analytics.js'],
259 premium: ['animations.js', 'advanced-features.js']
260 };
261
262 // Load based on device capability
263 this.loadScripts(scriptGroups.essential);
264
265 if (deviceClass !== 'low-end') {
266 setTimeout(() => {
267 this.loadScripts(scriptGroups.enhanced);
268 }, 1000);
269 }
270
271 if (deviceClass === 'high-end') {
272 setTimeout(() => {
273 this.loadScripts(scriptGroups.premium);
274 }, 2000);
275 }
276 }
277
278 setupFeatureLoading(deviceClass) {
279 // Disable expensive features on low-end devices
280 if (deviceClass === 'low-end') {
281 document.body.classList.add('reduced-features');
282
283 // Disable animations
284 document.querySelectorAll('[data-animation]').forEach(el => {
285 el.removeAttribute('data-animation');
286 });
287
288 // Simplify interactions
289 document.querySelectorAll('[data-complex-interaction]').forEach(el => {
290 el.classList.add('simple-interaction');
291 });
292 }
293
294 // Enable premium features for high-end devices
295 if (deviceClass === 'high-end') {
296 document.body.classList.add('premium-features');
297
298 // Enable advanced animations
299 this.enableAdvancedAnimations();
300
301 // Load interactive components
302 this.loadInteractiveComponents();
303 }
304 }
305
306 monitorOngoingPerformance() {
307 // Monitor frame rate
308 this.monitorFPS();
309
310 // Monitor memory usage
311 this.monitorMemoryUsage();
312
313 // Monitor network changes
314 this.monitorNetworkChanges();
315 }
316
317 monitorFPS() {
318 let frames = 0;
319 let lastTime = performance.now();
320
321 const checkFPS = () => {
322 frames++;
323 const currentTime = performance.now();
324
325 if (currentTime - lastTime >= 1000) {
326 const fps = frames;
327 frames = 0;
328 lastTime = currentTime;
329
330 // Adjust quality based on FPS
331 if (fps < 30) {
332 this.reduceQuality();
333 } else if (fps > 55 && this.profile.deviceClass !== 'high-end') {
334 this.increaseQuality();
335 }
336 }
337
338 requestAnimationFrame(checkFPS);
339 };
340
341 requestAnimationFrame(checkFPS);
342 }
343
344 monitorMemoryUsage() {
345 if (!performance.memory) return;
346
347 setInterval(() => {
348 const { usedJSHeapSize, jsHeapSizeLimit } = performance.memory;
349 const memoryUsage = usedJSHeapSize / jsHeapSizeLimit;
350
351 if (memoryUsage > 0.8) {
352 this.triggerMemoryCleanup();
353 }
354 }, 10000);
355 }
356
357 reduceQuality() {
358 document.body.classList.add('performance-mode');
359
360 // Disable expensive animations
361 document.querySelectorAll('[data-expensive-animation]').forEach(el => {
362 el.style.animation = 'none';
363 el.style.transition = 'none';
364 });
365
366 // Reduce image quality
367 document.querySelectorAll('img[data-high-quality]').forEach(img => {
368 const lowQualitySrc = img.dataset.lowQuality;
369 if (lowQualitySrc) {
370 img.src = lowQualitySrc;
371 }
372 });
373 }
374
375 loadScripts(scripts) {
376 scripts.forEach(script => {
377 const scriptElement = document.createElement('script');
378 scriptElement.src = `/js/${script}`;
379 scriptElement.async = true;
380 document.head.appendChild(scriptElement);
381 });
382 }
383}
384
385// Initialize performance profiling
386const performanceProfiler = new PerformanceProfiler();
387
388// Export for global access
389window.PerformanceProfiler = performanceProfiler;Adaptive Content Strategy:
1/* Performance-based content adaptation */
2.performance-mode .heavy-animation,
3.performance-mode .parallax-element,
4.performance-mode .complex-filter {
5 animation: none !important;
6 transform: none !important;
7 filter: none !important;
8}
9
10.performance-mode .video-background {
11 display: none;
12}
13
14.performance-mode .high-res-image {
15 display: none;
16}
17
18.performance-mode .low-res-fallback {
19 display: block;
20}
21
22/* Device-class specific optimizations */
23.device-low-end .optional-content {
24 display: none;
25}
26
27.device-low-end .essential-only {
28 display: block;
29}
30
31.device-high-end .premium-feature {
32 display: block;
33}
34
35.device-high-end .advanced-animation {
36 animation: sophisticated-effect 2s ease-in-out infinite;
37}
38
39@keyframes sophisticated-effect {
40 0%, 100% { transform: translateY(0) scale(1); }
41 50% { transform: translateY(-10px) scale(1.02); }
42}This comprehensive performance-based adaptive loading approach ensures that every user gets an experience optimized for their specific device capabilities, creating better performance and user satisfaction across the entire spectrum of devices and network conditions.
Build a comprehensive adaptive interface that detects and optimizes for device capabilities, input methods, screen characteristics, and performance constraints.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Device-Specific Optimizations Demo</title>
7 <style>
8 /* BASE STYLES */
9 * {
10 margin: 0;
11 padding: 0;
12 box-sizing: border-box;
13 }
14
15 body {
16 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
17 line-height: 1.6;
18 color: #333;
19 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
20 min-height: 100vh;
21 }
22
23 .container {
24 max-width: 1200px;
25 margin: 0 auto;
26 padding: 1rem;
27 }
28
29 .header {
30 text-align: center;
31 color: white;
32 padding: 2rem 0;
33 margin-bottom: 2rem;
34 }
35
36 .header h1 {
37 font-size: clamp(1.5rem, 5vw, 3rem);
38 margin-bottom: 0.5rem;
39 }
40
41 .device-info {
42 background: rgba(255, 255, 255, 0.95);
43 backdrop-filter: blur(10px);
44 border-radius: 16px;
45 padding: 2rem;
46 margin-bottom: 2rem;
47 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
48 }
49
50 .info-grid {
51 display: grid;
52 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
53 gap: 1.5rem;
54 margin-bottom: 2rem;
55 }
56
57 .info-card {
58 background: white;
59 border-radius: 12px;
60 padding: 1.5rem;
61 box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
62 transition: transform 0.3s ease, box-shadow 0.3s ease;
63 }
64
65 .info-card h3 {
66 color: #667eea;
67 margin-bottom: 1rem;
68 font-size: 1.25rem;
69 }
70
71 .capability-list {
72 list-style: none;
73 }
74
75 .capability-list li {
76 padding: 0.5rem 0;
77 border-bottom: 1px solid #f0f0f0;
78 display: flex;
79 justify-content: space-between;
80 align-items: center;
81 }
82
83 .capability-list li:last-child {
84 border-bottom: none;
85 }
86
87 .status {
88 font-weight: bold;
89 }
90
91 .status.supported {
92 color: #22c55e;
93 }
94
95 .status.not-supported {
96 color: #ef4444;
97 }
98
99 .status.unknown {
100 color: #f59e0b;
101 }
102
103 .adaptive-content {
104 display: grid;
105 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
106 gap: 2rem;
107 margin-bottom: 2rem;
108 }
109
110 .adaptive-card {
111 background: rgba(255, 255, 255, 0.95);
112 border-radius: 16px;
113 padding: 2rem;
114 backdrop-filter: blur(10px);
115 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
116 transition: all 0.3s ease;
117 }
118
119 .adaptive-card h3 {
120 color: #667eea;
121 margin-bottom: 1rem;
122 }
123
124 .interactive-demo {
125 margin-top: 2rem;
126 }
127
128 .demo-controls {
129 display: flex;
130 flex-wrap: wrap;
131 gap: 1rem;
132 margin-bottom: 1rem;
133 }
134
135 .btn {
136 padding: 0.75rem 1.5rem;
137 border: none;
138 border-radius: 8px;
139 background: #667eea;
140 color: white;
141 font-weight: 500;
142 cursor: pointer;
143 transition: all 0.2s ease;
144 min-height: 44px;
145 min-width: 44px;
146 }
147
148 .btn:hover {
149 background: #5a6fd8;
150 transform: translateY(-2px);
151 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
152 }
153
154 .btn:active {
155 transform: translateY(0);
156 }
157
158 .performance-indicator {
159 display: inline-block;
160 width: 12px;
161 height: 12px;
162 border-radius: 50%;
163 margin-right: 0.5rem;
164 }
165
166 .performance-high {
167 background: #22c55e;
168 box-shadow: 0 0 8px rgba(34, 197, 94, 0.4);
169 }
170
171 .performance-medium {
172 background: #f59e0b;
173 box-shadow: 0 0 8px rgba(245, 158, 11, 0.4);
174 }
175
176 .performance-low {
177 background: #ef4444;
178 box-shadow: 0 0 8px rgba(239, 68, 68, 0.4);
179 }
180
181 .fps-counter {
182 position: fixed;
183 top: 20px;
184 right: 20px;
185 background: rgba(0, 0, 0, 0.8);
186 color: white;
187 padding: 0.5rem 1rem;
188 border-radius: 8px;
189 font-family: monospace;
190 font-size: 0.875rem;
191 z-index: 1000;
192 }
193
194 /* INPUT METHOD OPTIMIZATIONS */
195
196 /* Touch optimizations */
197 @media (pointer: coarse) {
198 .btn, .interactive-element {
199 min-height: 48px;
200 min-width: 48px;
201 padding: 12px 16px;
202 }
203
204 .adaptive-card:active {
205 transform: scale(0.98);
206 }
207 }
208
209 /* Mouse optimizations */
210 @media (pointer: fine) and (hover: hover) {
211 .info-card:hover {
212 transform: translateY(-4px);
213 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
214 }
215
216 .btn:hover {
217 transform: translateY(-2px);
218 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
219 }
220 }
221
222 /* HIGH-DPI OPTIMIZATIONS */
223 @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
224 .high-res-content {
225 display: block;
226 }
227
228 .low-res-content {
229 display: none;
230 }
231 }
232
233 /* VIEWPORT OPTIMIZATIONS */
234
235 /* Ultra-wide screens */
236 @media (min-aspect-ratio: 21/9) {
237 .container {
238 max-width: 1600px;
239 }
240
241 .adaptive-content {
242 grid-template-columns: repeat(4, 1fr);
243 }
244 }
245
246 /* Portrait mobile */
247 @media (orientation: portrait) and (max-width: 768px) {
248 .info-grid {
249 grid-template-columns: 1fr;
250 }
251
252 .demo-controls {
253 flex-direction: column;
254 }
255 }
256
257 /* Short landscape (like phones in landscape) */
258 @media (orientation: landscape) and (max-height: 500px) {
259 .header {
260 padding: 1rem 0;
261 }
262
263 .info-card, .adaptive-card {
264 padding: 1rem;
265 }
266 }
267
268 /* PERFORMANCE OPTIMIZATIONS */
269
270 .performance-mode .expensive-animation {
271 animation: none !important;
272 transition: none !important;
273 }
274
275 .performance-mode .backdrop-blur {
276 backdrop-filter: none;
277 background: rgba(255, 255, 255, 0.95);
278 }
279
280 /* ACCESSIBILITY OPTIMIZATIONS */
281
282 @media (prefers-reduced-motion: reduce) {
283 * {
284 animation-duration: 0.01ms !important;
285 animation-iteration-count: 1 !important;
286 transition-duration: 0.01ms !important;
287 }
288 }
289
290 @media (prefers-color-scheme: dark) {
291 body {
292 background: linear-gradient(135deg, #1e3a8a 0%, #581c87 100%);
293 }
294
295 .device-info, .adaptive-card {
296 background: rgba(30, 30, 30, 0.95);
297 color: #f9fafb;
298 }
299
300 .info-card {
301 background: #374151;
302 color: #f9fafb;
303 }
304
305 .info-card h3, .adaptive-card h3 {
306 color: #93c5fd;
307 }
308 }
309
310 /* DEVICE CLASS SPECIFIC */
311
312 .device-low-end .premium-feature {
313 display: none;
314 }
315
316 .device-low-end .performance-heavy {
317 display: none;
318 }
319
320 .device-high-end .premium-animation {
321 animation: premium-float 3s ease-in-out infinite;
322 }
323
324 @keyframes premium-float {
325 0%, 100% { transform: translateY(0px); }
326 50% { transform: translateY(-10px); }
327 }
328
329 /* NETWORK OPTIMIZATIONS */
330
331 .slow-connection .heavy-content {
332 display: none;
333 }
334
335 .slow-connection .lightweight-alternative {
336 display: block;
337 }
338
339 /* RESPONSIVE UTILITIES */
340
341 .text-responsive {
342 font-size: clamp(0.875rem, 2.5vw, 1.125rem);
343 }
344
345 .spacing-responsive {
346 padding: clamp(1rem, 4vw, 2rem);
347 }
348
349 .width-responsive {
350 width: min(100%, 1200px);
351 }
352 </style>
353</head>
354<body>
355 <div class="fps-counter" id="fpsCounter">FPS: --</div>
356
357 <div class="container">
358 <header class="header">
359 <h1>๐ Device-Specific Optimizations</h1>
360 <p class="text-responsive">Adaptive interface that optimizes for your specific device capabilities</p>
361 </header>
362
363 <main>
364 <section class="device-info">
365 <h2>๐ฑ Your Device Profile</h2>
366 <div class="info-grid">
367 <div class="info-card">
368 <h3>Hardware Capabilities</h3>
369 <ul class="capability-list" id="hardwareCapabilities">
370 <li>Loading...</li>
371 </ul>
372 </div>
373
374 <div class="info-card">
375 <h3>Input Methods</h3>
376 <ul class="capability-list" id="inputCapabilities">
377 <li>Loading...</li>
378 </ul>
379 </div>
380
381 <div class="info-card">
382 <h3>Display Properties</h3>
383 <ul class="capability-list" id="displayCapabilities">
384 <li>Loading...</li>
385 </ul>
386 </div>
387
388 <div class="info-card">
389 <h3>Network & Performance</h3>
390 <ul class="capability-list" id="networkCapabilities">
391 <li>Loading...</li>
392 </ul>
393 </div>
394 </div>
395
396 <div id="deviceClassification">
397 <h3>Device Classification: <span id="deviceClass">Analyzing...</span></h3>
398 <div id="optimizationStatus"></div>
399 </div>
400 </section>
401
402 <section class="adaptive-content">
403 <div class="adaptive-card" id="touchDemo">
404 <h3>๐ Touch Optimization</h3>
405 <p>Interactive elements are optimized for your input method.</p>
406 <div class="interactive-demo">
407 <div class="demo-controls">
408 <button class="btn" data-interactive="tap">Tap Me</button>
409 <button class="btn" data-interactive="longpress">Long Press</button>
410 </div>
411 <div id="touchFeedback"></div>
412 </div>
413 </div>
414
415 <div class="adaptive-card premium-feature" id="visualDemo">
416 <h3>๐จ Visual Optimization</h3>
417 <p>Graphics adapt to your screen's pixel density and performance.</p>
418 <div class="high-res-content">
419 <p>โจ High-resolution content loaded</p>
420 </div>
421 <div class="low-res-content">
422 <p>๐ฑ Standard resolution content</p>
423 </div>
424 </div>
425
426 <div class="adaptive-card" id="performanceDemo">
427 <h3>โก Performance Adaptation</h3>
428 <p>Features adjust based on your device's performance capabilities.</p>
429 <div class="demo-controls">
430 <button class="btn" onclick="runPerformanceTest()">Test Performance</button>
431 <button class="btn" onclick="toggleFeatures()">Toggle Features</button>
432 </div>
433 <div id="performanceResults"></div>
434 </div>
435
436 <div class="adaptive-card" id="networkDemo">
437 <h3>๐ Network Optimization</h3>
438 <p>Content loading adapts to your network conditions.</p>
439 <div id="networkStatus"></div>
440 <div class="demo-controls">
441 <button class="btn" onclick="loadAdaptiveContent()">Load Content</button>
442 </div>
443 </div>
444 </section>
445 </main>
446 </div>
447
448 <script>
449 // Device Detection and Optimization System
450 class DeviceOptimizer {
451 constructor() {
452 this.profile = {};
453 this.performanceMetrics = {};
454 this.init();
455 }
456
457 async init() {
458 this.detectCapabilities();
459 await this.runPerformanceBenchmarks();
460 this.classifyDevice();
461 this.applyOptimizations();
462 this.setupMonitoring();
463 this.updateUI();
464 }
465
466 detectCapabilities() {
467 // Hardware detection
468 this.profile.hardware = {
469 memory: navigator.deviceMemory || 'Unknown',
470 cores: navigator.hardwareConcurrency || 1,
471 platform: navigator.platform,
472 maxTouchPoints: navigator.maxTouchPoints || 0
473 };
474
475 // Input methods
476 this.profile.input = {
477 hasTouch: 'ontouchstart' in window || navigator.maxTouchPoints > 0,
478 hasHover: matchMedia('(hover: hover)').matches,
479 pointerType: matchMedia('(pointer: fine)').matches ? 'fine' : 'coarse',
480 primaryInput: this.detectPrimaryInput()
481 };
482
483 // Display properties
484 this.profile.display = {
485 pixelRatio: window.devicePixelRatio || 1,
486 width: window.innerWidth,
487 height: window.innerHeight,
488 aspectRatio: (window.innerWidth / window.innerHeight).toFixed(2),
489 orientation: window.innerWidth > window.innerHeight ? 'landscape' : 'portrait',
490 colorDepth: screen.colorDepth,
491 isHighDPI: window.devicePixelRatio >= 2
492 };
493
494 // Network detection
495 const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
496 this.profile.network = {
497 effectiveType: connection?.effectiveType || 'Unknown',
498 downlink: connection?.downlink || 'Unknown',
499 rtt: connection?.rtt || 'Unknown',
500 saveData: connection?.saveData || false
501 };
502
503 // Feature detection
504 this.profile.features = {
505 intersectionObserver: 'IntersectionObserver' in window,
506 serviceWorker: 'serviceWorker' in navigator,
507 webGL: this.detectWebGL(),
508 localStorage: this.testLocalStorage(),
509 geolocation: 'geolocation' in navigator
510 };
511 }
512
513 detectPrimaryInput() {
514 if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
515 return 'touch';
516 }
517 if (matchMedia('(hover: hover) and (pointer: fine)').matches) {
518 return 'mouse';
519 }
520 return 'keyboard';
521 }
522
523 detectWebGL() {
524 try {
525 const canvas = document.createElement('canvas');
526 return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
527 } catch {
528 return false;
529 }
530 }
531
532 testLocalStorage() {
533 try {
534 localStorage.setItem('test', 'test');
535 localStorage.removeItem('test');
536 return true;
537 } catch {
538 return false;
539 }
540 }
541
542 async runPerformanceBenchmarks() {
543 // CPU benchmark
544 this.performanceMetrics.cpu = await this.benchmarkCPU();
545
546 // Rendering benchmark
547 this.performanceMetrics.rendering = await this.benchmarkRendering();
548
549 // Memory check
550 if (performance.memory) {
551 const { usedJSHeapSize, totalJSHeapSize } = performance.memory;
552 this.performanceMetrics.memoryUsage = (usedJSHeapSize / totalJSHeapSize * 100).toFixed(1);
553 }
554 }
555
556 async benchmarkCPU() {
557 return new Promise(resolve => {
558 const start = performance.now();
559
560 // CPU intensive task
561 let result = 0;
562 for (let i = 0; i < 50000; i++) {
563 result += Math.random() * Math.sin(i) * Math.cos(i);
564 }
565
566 const duration = performance.now() - start;
567
568 if (duration < 10) resolve('high');
569 else if (duration < 30) resolve('medium');
570 else resolve('low');
571 });
572 }
573
574 async benchmarkRendering() {
575 return new Promise(resolve => {
576 const canvas = document.createElement('canvas');
577 canvas.width = 200;
578 canvas.height = 200;
579 const ctx = canvas.getContext('2d');
580
581 const start = performance.now();
582
583 // Rendering test
584 for (let i = 0; i < 500; i++) {
585 ctx.fillStyle = `hsl(${i % 360}, 50%, 50%)`;
586 ctx.fillRect(Math.random() * 200, Math.random() * 200, 5, 5);
587 }
588
589 requestAnimationFrame(() => {
590 const duration = performance.now() - start;
591
592 if (duration < 15) resolve('high');
593 else if (duration < 50) resolve('medium');
594 else resolve('low');
595 });
596 });
597 }
598
599 classifyDevice() {
600 const scores = { high: 0, medium: 0, low: 0 };
601
602 // Hardware scoring
603 if (this.profile.hardware.memory >= 8) scores.high++;
604 else if (this.profile.hardware.memory >= 4) scores.medium++;
605 else scores.low++;
606
607 if (this.profile.hardware.cores >= 4) scores.high++;
608 else if (this.profile.hardware.cores >= 2) scores.medium++;
609 else scores.low++;
610
611 // Performance scoring
612 if (this.performanceMetrics.cpu === 'high') scores.high++;
613 else if (this.performanceMetrics.cpu === 'medium') scores.medium++;
614 else scores.low++;
615
616 if (this.performanceMetrics.rendering === 'high') scores.high++;
617 else if (this.performanceMetrics.rendering === 'medium') scores.medium++;
618 else scores.low++;
619
620 // Network impact
621 if (this.profile.network.effectiveType === '4g' && !this.profile.network.saveData) {
622 scores.high++;
623 } else if (this.profile.network.effectiveType === '3g') {
624 scores.medium++;
625 } else {
626 scores.low++;
627 }
628
629 const maxScore = Math.max(scores.high, scores.medium, scores.low);
630
631 if (scores.high === maxScore) {
632 this.profile.deviceClass = 'high-end';
633 } else if (scores.medium === maxScore) {
634 this.profile.deviceClass = 'mid-range';
635 } else {
636 this.profile.deviceClass = 'low-end';
637 }
638 }
639
640 applyOptimizations() {
641 const { deviceClass, network, input } = this.profile;
642
643 // Apply device class
644 document.body.classList.add(`device-${deviceClass}`);
645
646 // Apply input optimizations
647 document.body.classList.add(`input-${input.primaryInput}`);
648
649 // Apply network optimizations
650 if (network.saveData || network.effectiveType === 'slow-2g' || network.effectiveType === '2g') {
651 document.body.classList.add('slow-connection');
652 }
653
654 // Performance mode for low-end devices
655 if (deviceClass === 'low-end') {
656 document.body.classList.add('performance-mode');
657 }
658
659 // High-DPI optimizations
660 if (this.profile.display.isHighDPI) {
661 document.body.classList.add('high-dpi');
662 }
663 }
664
665 setupMonitoring() {
666 // FPS monitoring
667 this.startFPSMonitoring();
668
669 // Memory monitoring
670 if (performance.memory) {
671 setInterval(() => {
672 const { usedJSHeapSize, jsHeapSizeLimit } = performance.memory;
673 const usage = usedJSHeapSize / jsHeapSizeLimit;
674
675 if (usage > 0.8) {
676 console.warn('High memory usage detected');
677 this.enablePerformanceMode();
678 }
679 }, 10000);
680 }
681
682 // Network monitoring
683 if (navigator.connection) {
684 navigator.connection.addEventListener('change', () => {
685 this.updateNetworkOptimizations();
686 });
687 }
688 }
689
690 startFPSMonitoring() {
691 let frames = 0;
692 let lastTime = performance.now();
693
694 const measure = () => {
695 frames++;
696 const currentTime = performance.now();
697
698 if (currentTime - lastTime >= 1000) {
699 const fps = frames;
700 frames = 0;
701 lastTime = currentTime;
702
703 document.getElementById('fpsCounter').textContent = `FPS: ${fps}`;
704
705 if (fps < 30) {
706 this.enablePerformanceMode();
707 }
708 }
709
710 requestAnimationFrame(measure);
711 };
712
713 requestAnimationFrame(measure);
714 }
715
716 enablePerformanceMode() {
717 if (!document.body.classList.contains('performance-mode')) {
718 document.body.classList.add('performance-mode');
719 console.log('Performance mode enabled');
720 }
721 }
722
723 updateUI() {
724 this.updateHardwareDisplay();
725 this.updateInputDisplay();
726 this.updateDisplayProperties();
727 this.updateNetworkDisplay();
728 this.updateDeviceClassification();
729 }
730
731 updateHardwareDisplay() {
732 const container = document.getElementById('hardwareCapabilities');
733 const { hardware } = this.profile;
734
735 container.innerHTML = `740`;
741 }
742
743 updateInputDisplay() {
744 const container = document.getElementById('inputCapabilities');
745 const { input } = this.profile;
746
747 container.innerHTML = `752`;
753 }
754
755 updateDisplayProperties() {
756 const container = document.getElementById('displayCapabilities');
757 const { display } = this.profile;
758
759 container.innerHTML = `765`;
766 }
767
768 updateNetworkDisplay() {
769 const container = document.getElementById('networkCapabilities');
770 const { network } = this.profile;
771
772 container.innerHTML = `777`;
778 }
779
780 updateDeviceClassification() {
781 const classElement = document.getElementById('deviceClass');
782 const statusElement = document.getElementById('optimizationStatus');
783 const { deviceClass } = this.profile;
784
785 const performanceIndicator = deviceClass === 'high-end' ? 'performance-high' :
786 deviceClass === 'mid-range' ? 'performance-medium' : 'performance-low';
787
788 classElement.innerHTML = `791`;
792
793 statusElement.innerHTML = `801`;
802 }
803 }
804
805 // Touch interaction handling
806 function setupTouchDemo() {
807 const buttons = document.querySelectorAll('[data-interactive]');
808 const feedback = document.getElementById('touchFeedback');
809
810 buttons.forEach(button => {
811 let pressTimer;
812
813 button.addEventListener('pointerdown', (e) => {
814 const action = e.target.dataset.interactive;
815
816 if (action === 'longpress') {
817 pressTimer = setTimeout(() => {
818 feedback.innerHTML = '<p style="color: #22c55e;">โ
Long press detected!</p>';
819 if ('vibrate' in navigator) {
820 navigator.vibrate(50);
821 }
822 }, 500);
823 }
824 });
825
826 button.addEventListener('pointerup', (e) => {
827 const action = e.target.dataset.interactive;
828
829 if (action === 'tap' && !pressTimer) {
830 feedback.innerHTML = '<p style="color: #667eea;">๐ Tap detected!</p>';
831 if ('vibrate' in navigator) {
832 navigator.vibrate(10);
833 }
834 }
835
836 if (pressTimer) {
837 clearTimeout(pressTimer);
838 pressTimer = null;
839 }
840 });
841 });
842 }
843
844 // Performance testing
845 function runPerformanceTest() {
846 const resultsContainer = document.getElementById('performanceResults');
847 resultsContainer.innerHTML = '<p>Running performance test...</p>';
848
849 const start = performance.now();
850
851 // CPU test
852 let result = 0;
853 for (let i = 0; i < 100000; i++) {
854 result += Math.random() * Math.sin(i);
855 }
856
857 const cpuTime = performance.now() - start;
858
859 // Memory test
860 const memoryInfo = performance.memory ?
861 `${(performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(1)} MB` :
862 'Not available';
863
864 resultsContainer.innerHTML = `870`;
871 }
872
873 function toggleFeatures() {
874 const isPerfMode = document.body.classList.contains('performance-mode');
875
876 if (isPerfMode) {
877 document.body.classList.remove('performance-mode');
878 alert('Performance mode disabled - premium features enabled');
879 } else {
880 document.body.classList.add('performance-mode');
881 alert('Performance mode enabled - optimizing for speed');
882 }
883 }
884
885 function loadAdaptiveContent() {
886 const networkStatus = document.getElementById('networkStatus');
887 const connection = navigator.connection;
888
889 if (connection) {
890 const isSlowConnection = connection.effectiveType === 'slow-2g' ||
891 connection.effectiveType === '2g' ||
892 connection.saveData;
893
894 if (isSlowConnection) {
895 networkStatus.innerHTML = '<p style="color: #f59e0b;">โก Loading optimized content for slow connection...</p>';
896 setTimeout(() => {
897 networkStatus.innerHTML = '<p style="color: #22c55e;">โ
Lightweight content loaded successfully!</p>';
898 }, 1000);
899 } else {
900 networkStatus.innerHTML = '<p style="color: #667eea;">๐ Loading full-quality content...</p>';
901 setTimeout(() => {
902 networkStatus.innerHTML = '<p style="color: #22c55e;">โ
High-quality content loaded!</p>';
903 }, 2000);
904 }
905 } else {
906 networkStatus.innerHTML = '<p>Network information not available</p>';
907 }
908 }
909
910 // Initialize the device optimizer
911 const deviceOptimizer = new DeviceOptimizer();
912
913 // Setup demonstrations
914 setupTouchDemo();
915 </script>
916</body>
917</html>This comprehensive device-specific optimization system demonstrates how to create truly adaptive web experiences that respond to the full spectrum of device capabilities and user contexts.
๐ Device Detection Architecture:
1. Multi-Dimensional Device Profiling:
2. Device Classification System:
โก Performance Optimization Strategies:
3. Adaptive Resource Loading:
4. Input Method Optimizations:
๐ฑ Display Adaptations:
5. Screen Density Optimization:
6. Viewport-Specific Enhancements:
๐ Network-Aware Loading:
7. Connection-Based Optimizations:
โ๏ธ Runtime Performance Monitoring:
8. Live Performance Adaptation:
โฟ Accessibility Integration:
9. Universal Design Principles:
๐งช Interactive Demonstrations:
10. Real-Time Capability Testing:
๐ Technical Implementation Highlights:
This implementation demonstrates how modern web applications can intelligently adapt to provide optimal experiences across the entire spectrum of devices, from high-end desktop computers to entry-level smartphones, ensuring no user is left behind while maximizing the experience for capable devices.
Outstanding work mastering device-specific optimizations! You now understand how to create intelligent, adaptive interfaces that automatically optimize for any device, input method, or network condition. In our next lesson, we'll explore Performance on Mobile Devices, where you'll learn advanced techniques for optimizing loading times, battery usage, and user experience specifically for mobile environments. Get ready to master the nuances of mobile performance optimization!
Your progress is saved in this browser, on this device only.