From 150695ddbf25783e41b87bc5c4fb4bc7497cd159 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 12 Mar 2026 22:35:30 +0000 Subject: [PATCH 1/2] Initial plan From 5ed09c90bcd8007d1fced349bc72a7f7f7598392 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 12 Mar 2026 22:39:41 +0000 Subject: [PATCH 2/2] Complete unified codebase implementation with all safety features Co-authored-by: hashexplaindata <221828969+hashexplaindata@users.noreply.github.com> --- code/experiment.js | 206 +++++++++++++++++++++++++++++---------------- code/index.html | 25 +++--- code/style.css | 111 ++++++++++++++++++------ firebase.json | 5 +- 4 files changed, 235 insertions(+), 112 deletions(-) diff --git a/code/experiment.js b/code/experiment.js index 55bce07..651545b 100644 --- a/code/experiment.js +++ b/code/experiment.js @@ -14,18 +14,37 @@ const CFG = Object.freeze({ COLLECTION: 'conformity_telemetry' }); +// --- Participant ID Generation --- +function generatePID() { + try { + return self.crypto.randomUUID(); + } catch (e) { + return Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15); + } +} + // --- State Machine --- const STATE = { - pid: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), + pid: localStorage.getItem('experiment_pid') || generatePID(), condition: CFG.CONDITION, covariate: 0, currentTrial: 0, - results: [], // Tidy Data Long Format + results: [], trialStartTime: 0, isTrialActive: false, - justification: "" + justification: "", + activeScreen: null, + pendingTransitionTimer: null }; +// Persist PID to localStorage +try { + localStorage.setItem('experiment_pid', STATE.pid); +} catch (e) { + // Silent fallback +} + // --- Trial Definitions (Pixel-Perfect Components) --- // --- High-Fidelity, Zero-Latency UI Trials (Iqra University Context) --- const TRIALS = [ @@ -76,7 +95,7 @@ const TRIALS = [ `, - target: 'B' // Hypothesized preference for modern Bento grid layouts + target: 'B' }, { domain: 'Data Visualization (HEC Attendance Warning)', @@ -122,7 +141,7 @@ const TRIALS = [ Alert: You can only miss 2 more classes in RM-2 before facing HEC examination block. `, - target: 'A' + target: 'A' }, { domain: 'Financial Overview (Fee Voucher)', @@ -156,7 +175,7 @@ const TRIALS = [

Spring 2026 Invoice

Challan: IU-9938-26

- +
Due March 10 Rs. 95,500 @@ -242,7 +261,7 @@ const TRIALS = [

RM-2 Instructor Evaluation

"The instructor provided clear grading rubrics and feedback."

- +
@@ -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?

- +
@@ -122,13 +119,13 @@

Strategic Rationalization

Session Logged

Thank you for contributing to the Beta UI Diagnostic. Your telemetry has been securely hashed and synchronized.

- +
Synchronizing Payload...
@@ -138,7 +135,7 @@

Session Logged

- + diff --git a/code/style.css b/code/style.css index 2f088e8..8225592 100644 --- a/code/style.css +++ b/code/style.css @@ -8,6 +8,7 @@ --bg-void: #000000; --bg-card: #111111; --bg-surface: #1a1a1a; + --bg-main: var(--bg-void); --text-primary: #ffffff; --text-secondary: #a1a1a6; --accent-blue: #2997ff; @@ -31,9 +32,19 @@ body { font-family: 'Inter', -apple-system, sans-serif; background-color: var(--bg-void); color: var(--text-primary); - overflow: hidden; /* Prevent scrolling during experiment */ - height: 100vh; + overflow: hidden; + height: 100dvh; width: 100vw; + overscroll-behavior-y: none; + -webkit-user-select: none; + user-select: none; +} + +#display-pid, +.pid-display, +[data-selectable] { + -webkit-user-select: text; + user-select: text; } /* --- Screen Transitions --- */ @@ -92,6 +103,14 @@ h1 { font-size: 2.5rem; font-weight: 700; letter-spacing: -0.04em; margin-bottom h2 { font-size: 1.75rem; font-weight: 600; letter-spacing: -0.02em; margin-bottom: 1rem; } .subtitle { font-size: 1.1rem; color: var(--text-secondary); line-height: 1.5; margin-bottom: 2rem; } .instruction { color: var(--text-secondary); margin-bottom: 2rem; } +.step-indicator { font-size: 0.75rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 1rem; } + +/* --- Feature List --- */ +.feature-list { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 2rem; } +.feature-item { display: flex; align-items: center; gap: 0.75rem; font-size: 0.95rem; color: var(--text-secondary); } + +/* --- Options List --- */ +.option-list { display: flex; flex-direction: column; gap: 0.75rem; } /* --- Buttons --- */ .btn-primary { @@ -119,10 +138,10 @@ h2 { font-size: 1.75rem; font-weight: 600; letter-spacing: -0.02em; margin-botto border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 1rem; - margin-bottom: 0.75rem; cursor: pointer; transition: var(--transition); color: white; + font-size: 1rem; } .btn-familiarity:hover { background: rgba(255,255,255,0.08); border-color: var(--text-secondary); } @@ -130,6 +149,17 @@ h2 { font-size: 1.75rem; font-weight: 600; letter-spacing: -0.02em; margin-botto .btn-familiarity .val { font-weight: 700; font-size: 1.2rem; } .btn-familiarity .label { font-size: 0.9rem; font-weight: 500; } +.btn-secondary { + background: transparent; + border: 1px solid var(--border); + color: var(--text-secondary); + padding: 0.75rem 1.5rem; + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 1rem; + font-weight: 600; +} + /* --- Trial Grid & Bento Deception --- */ .trial-header { width: 100%; @@ -166,9 +196,21 @@ h2 { font-size: 1.75rem; font-weight: 600; letter-spacing: -0.02em; margin-botto .bento-grid-deception { grid-template-columns: 1fr 1fr; } } +.trial-footer { + width: 100%; + max-width: 900px; + margin-top: 1.5rem; + text-align: center; +} + +.trial-footer p { + font-size: 0.85rem; + color: var(--text-secondary); +} + /* --- Pixel-Perfect Geometric Mirroring --- */ .bento-choice-card { - position: relative; /* CRITICAL for absolute badge */ + position: relative; background: var(--bg-card); border: 2px solid var(--border); border-radius: var(--radius); @@ -178,7 +220,7 @@ h2 { font-size: 1.75rem; font-weight: 600; letter-spacing: -0.02em; margin-botto transition: var(--transition); display: flex; flex-direction: column; - overflow: hidden; /* For inner glow/shine effects */ + overflow: hidden; } .bento-choice-card::after { @@ -191,8 +233,8 @@ h2 { font-size: 1.75rem; font-weight: 600; letter-spacing: -0.02em; margin-botto pointer-events: none; } -.bento-choice-card:hover { - border-color: rgba(255, 255, 255, 0.2); +.bento-choice-card:hover { + border-color: rgba(255, 255, 255, 0.2); transform: translateY(-4px) scale(1.01); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6); } @@ -230,6 +272,9 @@ h2 { font-size: 1.75rem; font-weight: 600; letter-spacing: -0.02em; margin-botto box-shadow: 0 4px 15px rgba(168, 85, 247, 0.5); z-index: 10; pointer-events: none; + -webkit-font-smoothing: antialiased; + backface-visibility: hidden; + user-select: none; animation: badgePop 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); } @@ -238,20 +283,6 @@ h2 { font-size: 1.75rem; font-weight: 600; letter-spacing: -0.02em; margin-botto to { transform: scale(1); opacity: 1; } } -/* --- Mockup Components (Common) --- */ -.mock-header { height: 1.5rem; background: rgba(255,255,255,0.05); border-radius: 4px; margin-bottom: 1.5rem; width: 60%; } -.mock-block { background: rgba(255,255,255,0.03); border-radius: 8px; margin-bottom: 1rem; } - -/* --- Trial Specific Layouts --- */ -/* T1: Dashboard */ -.t1-kpi-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; margin-bottom: 1rem; } -.t1-kpi { height: 60px; background: rgba(255,255,255,0.03); border-radius: 12px; } -.t1-chart { flex: 1; background: rgba(255,255,255,0.02); border-radius: 12px; border: 1px dashed var(--border); } - -/* T2: Profile */ -.t2-avatar { width: 64px; height: 64px; border-radius: 32px; background: var(--accent-blue); margin-bottom: 1rem; } -.t2-line { height: 10px; background: rgba(255,255,255,0.05); border-radius: 5px; margin-bottom: 0.5rem; } - /* --- Screen 9: Justification --- */ textarea { width: 100%; @@ -273,9 +304,37 @@ textarea:focus { border-color: var(--accent-blue); } /* --- Screen 10: Outro --- */ .success-card { text-align: center; } -.success-icon { width: 80px; height: 80px; background: var(--accent-blue); border-radius: 40px; display: flex; align-items: center; justify-content: center; font-size: 2.5rem; margin: 0 auto 2rem; } -.sync-status { color: var(--text-secondary); font-size: 0.9rem; display: flex; align-items: center; justify-content: center; gap: 0.75rem; } -.spinner { width: 16px; height: 16px; border: 2px solid rgba(255,255,255,0.1); border-top-color: var(--accent-blue); border-radius: 50%; animation: spin 1s linear infinite; } +.success-icon { + width: 80px; + height: 80px; + background: var(--accent-blue); + border-radius: 40px; + display: flex; + align-items: center; + justify-content: center; + font-size: 2.5rem; + margin: 0 auto 2rem; +} +.sync-status { + color: var(--text-secondary); + font-size: 0.9rem; + display: flex; + align-items: center; + justify-content: center; + gap: 0.75rem; +} +.spinner { + width: 16px; + height: 16px; + border: 2px solid rgba(255,255,255,0.1); + border-top-color: var(--accent-blue); + border-radius: 50%; + animation: spin 1s linear infinite; +} @keyframes spin { to { transform: rotate(360deg); } } -.pid-display { font-family: monospace; color: var(--text-secondary); font-size: 0.8rem; margin: 2rem 0; } -.btn-secondary { background: transparent; border: 1px solid var(--border); color: var(--text-secondary); padding: 0.75rem 1.5rem; border-radius: var(--radius-sm); cursor: pointer; } +.pid-display { + font-family: monospace; + color: var(--text-secondary); + font-size: 0.8rem; + margin: 2rem 0; +} diff --git a/firebase.json b/firebase.json index 208b001..0b906c1 100644 --- a/firebase.json +++ b/firebase.json @@ -4,7 +4,10 @@ "ignore": [ "firebase.json", "**/.*", - "**/node_modules/**" + "**/node_modules/**", + "benchmark.html", + "**/*.test.*", + "telemetry_verification/**" ], "rewrites": [ {