|
| 1 | +/** |
| 2 | + * Feature detection for unsupported browsers (T066) |
| 3 | + * Detects critical features and shows fallback message if any are missing |
| 4 | + * Runs as a regular script (not type="module") to work even if ES modules fail |
| 5 | + */ |
| 6 | + |
| 7 | +(function() { |
| 8 | + 'use strict'; |
| 9 | + |
| 10 | + // Feature detection checks |
| 11 | + const features = { |
| 12 | + esModules: () => 'noModule' in HTMLScriptElement.prototype, |
| 13 | + canvas2d: () => { |
| 14 | + try { |
| 15 | + const canvas = document.createElement('canvas'); |
| 16 | + return canvas.getContext('2d') !== null; |
| 17 | + } catch (e) { |
| 18 | + return false; |
| 19 | + } |
| 20 | + }, |
| 21 | + localStorage: () => { |
| 22 | + try { |
| 23 | + const test = '__feature_test__'; |
| 24 | + localStorage.setItem(test, test); |
| 25 | + localStorage.removeItem(test); |
| 26 | + return true; |
| 27 | + } catch (e) { |
| 28 | + return false; |
| 29 | + } |
| 30 | + }, |
| 31 | + cssCustomProperties: () => { |
| 32 | + try { |
| 33 | + const el = document.createElement('div'); |
| 34 | + el.style.setProperty('--test', '1px'); |
| 35 | + return el.style.getPropertyValue('--test') === '1px'; |
| 36 | + } catch (e) { |
| 37 | + return false; |
| 38 | + } |
| 39 | + } |
| 40 | + }; |
| 41 | + |
| 42 | + // Check all critical features |
| 43 | + const results = {}; |
| 44 | + let allSupported = true; |
| 45 | + |
| 46 | + for (const [name, check] of Object.entries(features)) { |
| 47 | + try { |
| 48 | + results[name] = check(); |
| 49 | + if (!results[name]) { |
| 50 | + allSupported = false; |
| 51 | + } |
| 52 | + } catch (e) { |
| 53 | + results[name] = false; |
| 54 | + allSupported = false; |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + // If all features are supported, exit early |
| 59 | + if (allSupported) { |
| 60 | + window.__featureDetectionPassed = true; |
| 61 | + return; |
| 62 | + } |
| 63 | + |
| 64 | + // Show fallback message for unsupported browsers |
| 65 | + showUnsupportedBrowserFallback(results); |
| 66 | + |
| 67 | + function showUnsupportedBrowserFallback(results) { |
| 68 | + // Create fallback UI |
| 69 | + const fallback = document.createElement('div'); |
| 70 | + fallback.id = 'unsupported-browser-fallback'; |
| 71 | + fallback.style.cssText = ` |
| 72 | + position: fixed; |
| 73 | + inset: 0; |
| 74 | + background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); |
| 75 | + color: #f1f5f9; |
| 76 | + display: flex; |
| 77 | + align-items: center; |
| 78 | + justify-content: center; |
| 79 | + flex-direction: column; |
| 80 | + padding: 2rem; |
| 81 | + z-index: 99999; |
| 82 | + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; |
| 83 | + text-align: center; |
| 84 | + overflow-y: auto; |
| 85 | + `; |
| 86 | + |
| 87 | + const content = document.createElement('div'); |
| 88 | + content.style.cssText = ` |
| 89 | + max-width: 600px; |
| 90 | + background: rgba(30, 41, 59, 0.8); |
| 91 | + border: 1px solid rgba(51, 65, 85, 0.6); |
| 92 | + border-radius: 12px; |
| 93 | + padding: 3rem 2rem; |
| 94 | + backdrop-filter: blur(10px); |
| 95 | + `; |
| 96 | + |
| 97 | + const heading = document.createElement('h1'); |
| 98 | + heading.style.cssText = ` |
| 99 | + font-size: 2rem; |
| 100 | + margin-bottom: 1rem; |
| 101 | + font-weight: 700; |
| 102 | + color: #ffa00f; |
| 103 | + `; |
| 104 | + heading.textContent = 'Browser Not Supported'; |
| 105 | + |
| 106 | + const message = document.createElement('p'); |
| 107 | + message.style.cssText = ` |
| 108 | + font-size: 1.1rem; |
| 109 | + margin-bottom: 1.5rem; |
| 110 | + line-height: 1.6; |
| 111 | + color: #e2e8f0; |
| 112 | + `; |
| 113 | + message.textContent = 'Your browser is missing critical features needed to run the Knowledge Mapper.'; |
| 114 | + |
| 115 | + const details = document.createElement('div'); |
| 116 | + details.style.cssText = ` |
| 117 | + text-align: left; |
| 118 | + background: rgba(15, 23, 42, 0.5); |
| 119 | + border-radius: 8px; |
| 120 | + padding: 1.5rem; |
| 121 | + margin-bottom: 1.5rem; |
| 122 | + font-size: 0.95rem; |
| 123 | + color: #cbd5e1; |
| 124 | + `; |
| 125 | + |
| 126 | + const detailsList = document.createElement('ul'); |
| 127 | + detailsList.style.cssText = ` |
| 128 | + list-style: none; |
| 129 | + padding: 0; |
| 130 | + margin: 0; |
| 131 | + `; |
| 132 | + |
| 133 | + const featureNames = { |
| 134 | + esModules: 'ES Module Support', |
| 135 | + canvas2d: 'Canvas 2D Graphics', |
| 136 | + localStorage: 'Local Storage', |
| 137 | + cssCustomProperties: 'CSS Custom Properties' |
| 138 | + }; |
| 139 | + |
| 140 | + for (const [feature, supported] of Object.entries(results)) { |
| 141 | + const li = document.createElement('li'); |
| 142 | + li.style.cssText = ` |
| 143 | + padding: 0.5rem 0; |
| 144 | + display: flex; |
| 145 | + align-items: center; |
| 146 | + gap: 0.75rem; |
| 147 | + `; |
| 148 | + |
| 149 | + const status = document.createElement('span'); |
| 150 | + status.style.cssText = ` |
| 151 | + display: inline-block; |
| 152 | + width: 20px; |
| 153 | + height: 20px; |
| 154 | + border-radius: 50%; |
| 155 | + background: ${supported ? '#10b981' : '#ef4444'}; |
| 156 | + flex-shrink: 0; |
| 157 | + font-weight: bold; |
| 158 | + color: white; |
| 159 | + font-size: 0.8rem; |
| 160 | + display: flex; |
| 161 | + align-items: center; |
| 162 | + justify-content: center; |
| 163 | + `; |
| 164 | + status.textContent = supported ? '✓' : '✕'; |
| 165 | + |
| 166 | + const label = document.createElement('span'); |
| 167 | + label.textContent = featureNames[feature]; |
| 168 | + label.style.color = supported ? '#10b981' : '#ef4444'; |
| 169 | + |
| 170 | + li.appendChild(status); |
| 171 | + li.appendChild(label); |
| 172 | + detailsList.appendChild(li); |
| 173 | + } |
| 174 | + |
| 175 | + details.appendChild(detailsList); |
| 176 | + |
| 177 | + const recommendation = document.createElement('p'); |
| 178 | + recommendation.style.cssText = ` |
| 179 | + font-size: 1rem; |
| 180 | + margin-bottom: 1.5rem; |
| 181 | + line-height: 1.6; |
| 182 | + color: #cbd5e1; |
| 183 | + `; |
| 184 | + recommendation.innerHTML = 'Please upgrade to a modern browser:<br><strong>Chrome 90+</strong>, <strong>Firefox 88+</strong>, <strong>Safari 15+</strong>, or <strong>Edge 90+</strong>'; |
| 185 | + |
| 186 | + const button = document.createElement('button'); |
| 187 | + button.style.cssText = ` |
| 188 | + background: linear-gradient(135deg, #00693e 0%, #1a8a5a 100%); |
| 189 | + color: white; |
| 190 | + border: none; |
| 191 | + padding: 0.75rem 2rem; |
| 192 | + border-radius: 8px; |
| 193 | + font-size: 1rem; |
| 194 | + font-weight: 600; |
| 195 | + cursor: pointer; |
| 196 | + transition: all 0.3s ease; |
| 197 | + box-shadow: 0 4px 12px rgba(0, 105, 62, 0.3); |
| 198 | + `; |
| 199 | + button.textContent = 'Try Anyway (May Not Work)'; |
| 200 | + button.onmouseover = () => { |
| 201 | + button.style.boxShadow = '0 6px 16px rgba(0, 105, 62, 0.5)'; |
| 202 | + button.style.transform = 'translateY(-2px)'; |
| 203 | + }; |
| 204 | + button.onmouseout = () => { |
| 205 | + button.style.boxShadow = '0 4px 12px rgba(0, 105, 62, 0.3)'; |
| 206 | + button.style.transform = 'translateY(0)'; |
| 207 | + }; |
| 208 | + button.onclick = () => { |
| 209 | + fallback.remove(); |
| 210 | + window.__featureDetectionPassed = true; |
| 211 | + }; |
| 212 | + |
| 213 | + content.appendChild(heading); |
| 214 | + content.appendChild(message); |
| 215 | + content.appendChild(details); |
| 216 | + content.appendChild(recommendation); |
| 217 | + content.appendChild(button); |
| 218 | + |
| 219 | + fallback.appendChild(content); |
| 220 | + document.body.appendChild(fallback); |
| 221 | + |
| 222 | + // Prevent app from loading |
| 223 | + window.__featureDetectionPassed = false; |
| 224 | + } |
| 225 | +})(); |
0 commit comments