Skip to content

Commit 3704f88

Browse files
jeremymanningclaude
andcommitted
fix: add missing files and copy data to dist for CI build
- Add src/ui/share.js (share modal, imported by app.js but not committed) - Add public/feature-detection.js (browser capability check, static asset) - Add src/utils/feature-detection.js (source version) - Update build script to copy data/ directory into dist/ so domain JSON bundles are available at runtime on GitHub Pages Co-Authored-By: Claude (claude-opus-4-6) <noreply@anthropic.com>
1 parent fae9ae4 commit 3704f88

File tree

4 files changed

+872
-1
lines changed

4 files changed

+872
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"description": "Interactive Wikipedia Knowledge Map — adaptive quiz visualization using text embeddings",
66
"scripts": {
77
"dev": "vite",
8-
"build": "vite build",
8+
"build": "vite build && cp -r data dist/data",
99
"preview": "vite preview",
1010
"test": "vitest run",
1111
"bench": "vitest bench",

public/feature-detection.js

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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

Comments
 (0)