@@ -293,7 +312,6 @@ const TRIALS = [
// --- DOM Elements ---
const DOM = {
- screens: document.querySelectorAll('.screen'),
btnConsent: document.getElementById('btn-consent'),
btnsFamiliarity: document.querySelectorAll('.btn-familiarity'),
trialGrid: document.getElementById('trial-grid'),
@@ -308,34 +326,49 @@ const DOM = {
// --- Navigation Logic ---
function showScreen(id) {
- DOM.screens.forEach(s => {
- s.classList.remove('active');
- s.style.display = 'none';
- });
- const target = document.getElementById(`screen-${id}`);
+ const target = document.getElementById('screen-' + id);
+ if (!target || target === STATE.activeScreen) return;
+
+ const outgoing = STATE.activeScreen;
+ STATE.activeScreen = target;
+
+ if (outgoing) {
+ outgoing.classList.remove('active');
+ setTimeout(() => { outgoing.style.display = 'none'; }, 400);
+ }
+
target.style.display = 'flex';
- setTimeout(() => target.classList.add('active'), 50);
+ if (STATE.pendingTransitionTimer) clearTimeout(STATE.pendingTransitionTimer);
+ STATE.pendingTransitionTimer = setTimeout(() => {
+ target.classList.add('active');
+ STATE.pendingTransitionTimer = null;
+ }, 50);
}
// --- Experiment Logic ---
function init() {
+ STATE.activeScreen = document.querySelector('.screen.active');
+
// Navigation Lock
- window.history.pushState(null, "", window.location.href);
- window.onpopstate = () => window.history.pushState(null, "", window.location.href);
+ window.history.replaceState(null, document.title, window.location.href);
+ window.history.pushState(null, document.title, window.location.href);
+ window.addEventListener('popstate', () => {
+ window.history.go(1);
+ });
// Screen 1 Event
DOM.btnConsent.addEventListener('click', () => showScreen(2));
// Screen 2 Event
- let covariateSelected = false;
- DOM.btnsFamiliarity.forEach(btn => {
- btn.addEventListener('click', () => {
- if (covariateSelected) return; // Prevent double-tap
- covariateSelected = true;
- STATE.covariate = parseInt(btn.dataset.val);
- showScreen('trial');
- loadNextTrial();
- });
+ let covariateSelected = false;
+ DOM.btnsFamiliarity.forEach(btn => {
+ btn.addEventListener('click', () => {
+ if (covariateSelected) return;
+ covariateSelected = true;
+ STATE.covariate = parseInt(btn.dataset.val);
+ showScreen('trial');
+ loadNextTrial();
+ });
});
// Screen 9 Events
@@ -344,12 +377,11 @@ function init() {
});
DOM.btnFinalize.addEventListener('click', () => {
+ DOM.btnFinalize.disabled = true;
STATE.justification = DOM.textareaJustification.value.trim();
showScreen(10);
executeBatchPayload();
});
-
- console.log(`Diagnostic Engine Initialized. PID: ${STATE.pid} | Condition: ${STATE.condition}`);
}
function loadNextTrial() {
@@ -359,43 +391,39 @@ function loadNextTrial() {
}
const trial = TRIALS[STATE.currentTrial];
- DOM.trialCounter.innerText = `Diagnostic ${STATE.currentTrial + 1}/${CFG.NUM_TRIALS}`;
+ DOM.trialCounter.textContent = `Diagnostic ${STATE.currentTrial + 1}/${CFG.NUM_TRIALS}`;
DOM.progressFill.style.width = `${(STATE.currentTrial / CFG.NUM_TRIALS) * 100}%`;
- // Randomize L/R positioning to prevent motor habituation
const leftIsA = Math.random() > 0.5;
-
- // Build the Bento Choice Cards
+
DOM.trialGrid.innerHTML = '';
-
+
const cardL = createChoiceCard(leftIsA ? 'A' : 'B', trial);
const cardR = createChoiceCard(leftIsA ? 'B' : 'A', trial);
-
+
DOM.trialGrid.appendChild(cardL);
DOM.trialGrid.appendChild(cardR);
- // Inject AI Badge for experimental condition
if (STATE.condition === 'ai_labeled') {
- // Find which card is the "target" layout (A or B)
const targetType = trial.target;
-
- // Find the DOM element for that specific layout type
- const targetCard = (leftIsA && targetType === 'A') || (!leftIsA && targetType === 'B')
- ? cardL
+ const targetCard = (leftIsA && targetType === 'A') || (!leftIsA && targetType === 'B')
+ ? cardL
: cardR;
-
+
const badge = document.createElement('div');
badge.className = 'ai-recommendation-badge';
- badge.innerHTML = '
✨ AI Recommended';
+ const badgeSpan = document.createElement('span');
+ badgeSpan.textContent = '✨';
+ badge.appendChild(badgeSpan);
+ badge.appendChild(document.createTextNode(' AI Recommended'));
targetCard.appendChild(badge);
}
- // Start millisecond-accurate timer
- requestAnimationFrame(() => {
- requestAnimationFrame(() => {
- STATE.trialStartTime = performance.now();
- STATE.isTrialActive = true;
- });
+ requestAnimationFrame(() => {
+ setTimeout(() => {
+ STATE.trialStartTime = performance.now();
+ STATE.isTrialActive = true;
+ }, 0);
});
}
@@ -403,8 +431,7 @@ function createChoiceCard(type, trial) {
const card = document.createElement('div');
card.className = 'bento-choice-card';
card.innerHTML = type === 'A' ? trial.renderA() : trial.renderB();
-
- // Add mouse move listener for the radial glow effect
+
card.addEventListener('mousemove', (e) => {
const rect = card.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
@@ -413,23 +440,26 @@ function createChoiceCard(type, trial) {
card.style.setProperty('--mouse-y', `${y}%`);
});
- card.addEventListener('pointerdown', () => {
+ const handlePointerDown = (e) => {
if (!STATE.isTrialActive) return;
-
- // Visual feedback
+
+ e.preventDefault();
+ STATE.isTrialActive = false;
+
+ card.removeEventListener('pointerdown', handlePointerDown);
card.classList.add('selected');
-
+
handleUserSelection(type, trial);
- });
-
+ };
+
+ card.addEventListener('pointerdown', handlePointerDown);
+
return card;
}
function handleUserSelection(selection, trial) {
- const rt = performance.now() - STATE.trialStartTime;
- STATE.isTrialActive = false;
+ const latency = performance.now() - STATE.trialStartTime;
- // Log Tidy Data Row
STATE.results.push({
participant_id: STATE.pid,
experimental_condition: STATE.condition,
@@ -439,26 +469,37 @@ function handleUserSelection(selection, trial) {
ai_badge_position: STATE.condition === 'ai_labeled' ? `Layout ${trial.target}` : 'none',
user_selection: `Layout ${selection}`,
chose_target_layout: selection === trial.target,
- reaction_time_ms: parseFloat(rt.toFixed(2)),
- semantic_justification: null, // Placeholder
+ response_latency_ms: parseFloat(latency.toFixed(2)),
timestamp: Date.now()
});
STATE.currentTrial++;
-
- // Debounce transition for visual feedback
+
setTimeout(loadNextTrial, 200);
}
// --- Firebase Integration (Batch Write) ---
async function executeBatchPayload() {
- // Append justification to all rows
STATE.results.forEach(row => row.semantic_justification = STATE.justification);
+ let localBackupSucceeded = false;
try {
- // Check for Firebase (initialized in index.html via firebase-config.js)
- if (typeof firebase !== 'undefined' && firebase.apps.length > 0) {
+ localStorage.setItem('telemetry_backup_' + STATE.pid, JSON.stringify(STATE.results));
+ localBackupSucceeded = true;
+ } catch (storageError) {
+ // Silent fallback
+ }
+
+ try {
+ if (typeof firebase !== 'undefined' && firebase.apps && firebase.apps.length > 0) {
const db = firebase.firestore();
+
+ try {
+ await db.enablePersistence({ synchronizeTabs: true });
+ } catch (persistErr) {
+ // Silent fallback
+ }
+
const batch = db.batch();
STATE.results.forEach(data => {
@@ -467,22 +508,45 @@ async function executeBatchPayload() {
});
await batch.commit();
+ await db.waitForPendingWrites();
+
+ try { localStorage.removeItem('telemetry_backup_' + STATE.pid); } catch(e) {}
+ try { localStorage.removeItem('experiment_pid'); } catch(e) {}
+
onSyncSuccess();
} else {
- console.warn("Firebase not detected. Payload logged to console:", STATE.results);
- setTimeout(onSyncSuccess, 1500); // Simulate sync delay
+ if (!localBackupSucceeded) {
+ try {
+ localStorage.setItem('telemetry_backup_' + STATE.pid, JSON.stringify(STATE.results));
+ } catch (storageError) {
+ // Silent fallback
+ }
+ }
+ setTimeout(onSyncSuccess, 0);
}
} catch (error) {
- console.error("Critical Sync Failure:", error);
- DOM.syncStatus.innerHTML = `
⚠️ Sync Failed. Error: ${error.code || 'Network'}`;
- // Potential fallback: Save to localStorage for later recovery
+ if (!localBackupSucceeded) {
+ try {
+ localStorage.setItem('telemetry_backup_' + STATE.pid, JSON.stringify(STATE.results));
+ localBackupSucceeded = true;
+ } catch (storageError) {
+ // Silent fallback
+ }
+ }
+
+ DOM.syncStatus.textContent = localBackupSucceeded
+ ? '⚠️ Network Timeout — your responses are saved locally'
+ : '⚠️ Network Timeout';
+ DOM.syncStatus.style.color = '#ff453a';
+ DOM.finalActions.style.display = 'block';
+ DOM.displayPid.textContent = STATE.pid;
}
}
function onSyncSuccess() {
DOM.syncStatus.style.display = 'none';
DOM.finalActions.style.display = 'block';
- DOM.displayPid.innerText = STATE.pid;
+ DOM.displayPid.textContent = STATE.pid;
}
// Initialize on Load
diff --git a/code/index.html b/code/index.html
index d1174fa..f45d3e5 100644
--- a/code/index.html
+++ b/code/index.html
@@ -3,27 +3,24 @@
-
+
Beta UI Diagnostic Dashboard — University Portal
-
+
-
+
-
+
@@ -55,7 +52,7 @@
Beta UI Diagnostic
Step 1 of 2
Baseline Familiarity
How often do you utilize Generative AI tools (e.g., ChatGPT, Midjourney, Copilot) in your workflow?
-
+
Thank you for contributing to the Beta UI Diagnostic. Your telemetry has been securely hashed and synchronized.