638 lines
20 KiB
HTML
638 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>GooseFactory — Side-by-Side Arena</title>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
:root {
|
|
--bg: #111;
|
|
--card: #1a1a2e;
|
|
--text: #e0e0e0;
|
|
--muted: #777;
|
|
--blue: #4488FF;
|
|
--orange: #FF8844;
|
|
--gold: #FFD700;
|
|
--radius: 14px;
|
|
}
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* ── VS Header ── */
|
|
.vs-header {
|
|
text-align: center;
|
|
padding: 16px 16px 12px;
|
|
position: relative;
|
|
}
|
|
.vs-header .review-label {
|
|
font-size: 10px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 2px;
|
|
color: var(--muted);
|
|
margin-bottom: 4px;
|
|
}
|
|
.vs-header .review-title {
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* ── Arena Grid ── */
|
|
.arena {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto 1fr;
|
|
gap: 0;
|
|
padding: 0 12px;
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
.contender {
|
|
border-radius: var(--radius);
|
|
padding: 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
transition: all 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
cursor: pointer;
|
|
position: relative;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
.contender:hover { transform: scale(1.01); }
|
|
|
|
.contender-a {
|
|
border: 2px solid color-mix(in srgb, var(--blue) 50%, #333);
|
|
background: color-mix(in srgb, var(--blue) 5%, var(--card));
|
|
margin-right: -4px;
|
|
}
|
|
.contender-b {
|
|
border: 2px solid color-mix(in srgb, var(--orange) 50%, #333);
|
|
background: color-mix(in srgb, var(--orange) 5%, var(--card));
|
|
margin-left: -4px;
|
|
}
|
|
|
|
.contender .c-badge {
|
|
font-size: 11px;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1.5px;
|
|
margin-bottom: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
.contender-a .c-badge { color: var(--blue); }
|
|
.contender-b .c-badge { color: var(--orange); }
|
|
|
|
.contender .c-name {
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
margin-bottom: 8px;
|
|
line-height: 1.3;
|
|
}
|
|
.contender .c-preview {
|
|
font-size: 12px;
|
|
color: var(--muted);
|
|
line-height: 1.5;
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
max-height: 200px;
|
|
}
|
|
.contender .c-preview::-webkit-scrollbar { width: 4px; }
|
|
.contender .c-preview::-webkit-scrollbar-thumb { background: #444; border-radius: 2px; }
|
|
|
|
.contender .c-metrics {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
margin-top: 10px;
|
|
padding-top: 10px;
|
|
border-top: 1px solid rgba(255,255,255,0.06);
|
|
}
|
|
.c-metric {
|
|
font-size: 10px;
|
|
background: rgba(255,255,255,0.06);
|
|
padding: 3px 8px;
|
|
border-radius: 6px;
|
|
color: var(--muted);
|
|
}
|
|
|
|
/* Winner / Loser states */
|
|
.contender.winner {
|
|
border-color: var(--gold);
|
|
box-shadow: 0 0 30px color-mix(in srgb, var(--gold) 25%, transparent);
|
|
transform: scale(1.02);
|
|
animation: winner-glow 1.2s ease-in-out;
|
|
}
|
|
.contender.loser {
|
|
opacity: 0.3;
|
|
transform: scale(0.97);
|
|
filter: grayscale(60%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
@keyframes winner-glow {
|
|
0% { box-shadow: 0 0 0 transparent; }
|
|
50% { box-shadow: 0 0 50px color-mix(in srgb, var(--gold) 40%, transparent); }
|
|
100% { box-shadow: 0 0 30px color-mix(in srgb, var(--gold) 25%, transparent); }
|
|
}
|
|
|
|
/* ── VS Badge ── */
|
|
.vs-badge {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0 6px;
|
|
z-index: 10;
|
|
}
|
|
.vs-circle {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 50%;
|
|
background: #222;
|
|
border: 2px solid #444;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 14px;
|
|
font-weight: 900;
|
|
color: #fff;
|
|
text-shadow: 0 0 8px rgba(255,255,255,0.3);
|
|
animation: vs-pulse 2s ease-in-out infinite;
|
|
}
|
|
@keyframes vs-pulse {
|
|
0%, 100% { box-shadow: 0 0 8px rgba(255,255,255,0.1); }
|
|
50% { box-shadow: 0 0 16px rgba(255,255,255,0.2); }
|
|
}
|
|
.vs-circle.decided {
|
|
animation: none;
|
|
background: var(--gold);
|
|
border-color: var(--gold);
|
|
color: #000;
|
|
}
|
|
|
|
/* ── Winner Selection Row ── */
|
|
.verdict-section {
|
|
padding: 16px 12px;
|
|
}
|
|
.verdict-label {
|
|
text-align: center;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1.5px;
|
|
color: var(--muted);
|
|
margin-bottom: 12px;
|
|
}
|
|
.winner-buttons {
|
|
display: flex;
|
|
gap: 8px;
|
|
justify-content: center;
|
|
}
|
|
.win-btn {
|
|
padding: 12px 24px;
|
|
border-radius: 12px;
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
border: 2px solid;
|
|
background: transparent;
|
|
transition: all 0.25s;
|
|
min-height: 48px;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
.win-btn:active { transform: scale(0.96); }
|
|
.a-btn { border-color: var(--blue); color: var(--blue); }
|
|
.a-btn:hover { background: color-mix(in srgb, var(--blue) 15%, transparent); }
|
|
.a-btn.selected { background: var(--blue); color: #fff; }
|
|
.tie-btn { border-color: #666; color: #999; font-size: 13px; }
|
|
.tie-btn:hover { background: rgba(255,255,255,0.05); }
|
|
.tie-btn.selected { background: #666; color: #fff; }
|
|
.b-btn { border-color: var(--orange); color: var(--orange); }
|
|
.b-btn:hover { background: color-mix(in srgb, var(--orange) 15%, transparent); }
|
|
.b-btn.selected { background: var(--orange); color: #fff; }
|
|
|
|
/* ── Why Input ── */
|
|
.why-section {
|
|
padding: 0 12px 12px;
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
transition: all 0.35s;
|
|
pointer-events: none;
|
|
}
|
|
.why-section.visible {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
pointer-events: auto;
|
|
}
|
|
.why-chips {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
justify-content: center;
|
|
margin-bottom: 10px;
|
|
}
|
|
.why-chip {
|
|
padding: 7px 14px;
|
|
border-radius: 18px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
border: 1.5px solid #444;
|
|
background: var(--card);
|
|
color: var(--text);
|
|
transition: all 0.2s;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
.why-chip:hover { border-color: #888; }
|
|
.why-chip.active { border-color: var(--gold); color: var(--gold); background: color-mix(in srgb, var(--gold) 10%, var(--card)); }
|
|
|
|
.why-input {
|
|
width: 100%;
|
|
background: var(--card);
|
|
border: 1.5px solid #333;
|
|
border-radius: 10px;
|
|
color: var(--text);
|
|
font-family: inherit;
|
|
font-size: 13px;
|
|
padding: 10px 14px;
|
|
resize: none;
|
|
outline: none;
|
|
transition: border-color 0.25s;
|
|
}
|
|
.why-input:focus { border-color: #555; }
|
|
.why-input::placeholder { color: #555; }
|
|
|
|
/* ── Submit ── */
|
|
.submit-row {
|
|
padding: 12px;
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
transition: all 0.35s 0.1s;
|
|
pointer-events: none;
|
|
}
|
|
.submit-row.visible {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
pointer-events: auto;
|
|
}
|
|
.submit-btn {
|
|
width: 100%;
|
|
padding: 14px;
|
|
border: none;
|
|
border-radius: 12px;
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
background: linear-gradient(135deg, #b8860b, #daa520);
|
|
color: #fff;
|
|
transition: all 0.25s;
|
|
}
|
|
.submit-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.4); }
|
|
.submit-btn:active { transform: scale(0.98); }
|
|
|
|
/* ── Dimension Breakdown Toggle ── */
|
|
.dimension-toggle {
|
|
text-align: center;
|
|
padding: 8px 12px 4px;
|
|
}
|
|
.dim-toggle-btn {
|
|
font-size: 11px;
|
|
background: none;
|
|
border: none;
|
|
color: var(--muted);
|
|
cursor: pointer;
|
|
padding: 4px 12px;
|
|
border-radius: 12px;
|
|
transition: color 0.2s;
|
|
}
|
|
.dim-toggle-btn:hover { color: var(--text); }
|
|
|
|
.dim-breakdown {
|
|
padding: 0 12px 8px;
|
|
max-height: 0;
|
|
overflow: hidden;
|
|
transition: max-height 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
}
|
|
.dim-breakdown.open { max-height: 400px; }
|
|
|
|
.dim-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 6px 0;
|
|
font-size: 12px;
|
|
}
|
|
.dim-name { flex: 1; color: var(--muted); }
|
|
.dim-pick {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
.dim-pick-btn {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 50%;
|
|
border: 1.5px solid #444;
|
|
background: transparent;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
color: var(--muted);
|
|
}
|
|
.dim-pick-btn:hover { border-color: #888; }
|
|
.dim-pick-btn.a-pick.active { background: var(--blue); border-color: var(--blue); color: #fff; }
|
|
.dim-pick-btn.tie-pick.active { background: #666; border-color: #666; color: #fff; }
|
|
.dim-pick-btn.b-pick.active { background: var(--orange); border-color: var(--orange); color: #fff; }
|
|
|
|
/* ── Done ── */
|
|
.done-overlay {
|
|
position: fixed; inset: 0; background: var(--bg);
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
opacity: 0; pointer-events: none; transition: opacity 0.5s; z-index: 100;
|
|
}
|
|
.done-overlay.visible { opacity: 1; pointer-events: auto; }
|
|
.done-overlay .done-emoji { font-size: 56px; margin-bottom: 12px; }
|
|
.done-overlay .done-text { font-size: 18px; color: var(--muted); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="vs-header">
|
|
<div class="review-label">Compare & Choose</div>
|
|
<div class="review-title" id="reviewTitle">Which approach is better?</div>
|
|
</div>
|
|
|
|
<div class="arena" id="arena">
|
|
<div class="contender contender-a" id="contenderA" tabindex="0">
|
|
<div class="c-badge">🔵 Option A</div>
|
|
<div class="c-name" id="nameA">Approach A</div>
|
|
<div class="c-preview" id="previewA">Loading content…</div>
|
|
<div class="c-metrics" id="metricsA"></div>
|
|
</div>
|
|
|
|
<div class="vs-badge">
|
|
<div class="vs-circle" id="vsCircle">VS</div>
|
|
</div>
|
|
|
|
<div class="contender contender-b" id="contenderB" tabindex="0">
|
|
<div class="c-badge">🟠 Option B</div>
|
|
<div class="c-name" id="nameB">Approach B</div>
|
|
<div class="c-preview" id="previewB">Loading content…</div>
|
|
<div class="c-metrics" id="metricsB"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dimension-toggle">
|
|
<button class="dim-toggle-btn" id="dimToggle">▾ Per-dimension breakdown</button>
|
|
</div>
|
|
<div class="dim-breakdown" id="dimBreakdown"></div>
|
|
|
|
<div class="verdict-section">
|
|
<div class="verdict-label">And the winner is…</div>
|
|
<div class="winner-buttons">
|
|
<button class="win-btn a-btn" data-winner="A" id="btnA">🔵 A</button>
|
|
<button class="win-btn tie-btn" data-winner="tie" id="btnTie">🤝 Tie</button>
|
|
<button class="win-btn b-btn" data-winner="B" id="btnB">🟠 B</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="why-section" id="whySection">
|
|
<div class="why-chips" id="whyChips">
|
|
<button class="why-chip" data-why="Cleaner">Cleaner</button>
|
|
<button class="why-chip" data-why="Faster">Faster</button>
|
|
<button class="why-chip" data-why="Simpler">Simpler</button>
|
|
<button class="why-chip" data-why="More Complete">More Complete</button>
|
|
<button class="why-chip" data-why="Better DX">Better DX</button>
|
|
<button class="why-chip" data-why="Better Error Handling">Error Handling</button>
|
|
</div>
|
|
<textarea class="why-input" id="whyInput" placeholder="Why did this one win? (optional)" rows="2" maxlength="200"></textarea>
|
|
</div>
|
|
|
|
<div class="submit-row" id="submitRow">
|
|
<button class="submit-btn" id="submitBtn">Submit Verdict</button>
|
|
</div>
|
|
|
|
<div class="done-overlay" id="doneOverlay">
|
|
<div class="done-emoji">🏆</div>
|
|
<div class="done-text" id="doneText">Verdict submitted</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
const ctx = window.__FACTORY_CONTEXT__ || {};
|
|
const modalType = 'side-by-side';
|
|
const modalVersion = '1.0.0';
|
|
const startTime = Date.now();
|
|
|
|
function post(msg) { try { window.parent.postMessage(msg, '*'); } catch(e) {} }
|
|
post({ type: 'factory_modal_ready', modalType, version: modalVersion });
|
|
|
|
// ── Populate content ──
|
|
const itemA = (ctx.items && ctx.items[0]) || { name: 'Token Bucket Rate Limiter', preview: 'Classic token bucket algorithm. Refills at fixed rate. Simple to implement, predictable behavior. O(1) per request. Works well for steady traffic patterns.' };
|
|
const itemB = (ctx.items && ctx.items[1]) || { name: 'Sliding Window Counter', preview: 'Sliding window approach with sub-window counters. More accurate near window boundaries. Slightly more complex but handles bursty traffic better.' };
|
|
|
|
document.getElementById('nameA').textContent = itemA.name || 'Option A';
|
|
document.getElementById('nameB').textContent = itemB.name || 'Option B';
|
|
document.getElementById('previewA').textContent = itemA.preview || itemA.content || '';
|
|
document.getElementById('previewB').textContent = itemB.preview || itemB.content || '';
|
|
document.getElementById('reviewTitle').textContent = ctx.itemName || 'Which approach is better?';
|
|
|
|
// ── Hover / attention tracking ──
|
|
let hoverTimeA = 0;
|
|
let hoverTimeB = 0;
|
|
let hoverStartA = null;
|
|
let hoverStartB = null;
|
|
|
|
document.getElementById('contenderA').addEventListener('mouseenter', () => { hoverStartA = Date.now(); });
|
|
document.getElementById('contenderA').addEventListener('mouseleave', () => {
|
|
if (hoverStartA) { hoverTimeA += Date.now() - hoverStartA; hoverStartA = null; }
|
|
});
|
|
document.getElementById('contenderB').addEventListener('mouseenter', () => { hoverStartB = Date.now(); });
|
|
document.getElementById('contenderB').addEventListener('mouseleave', () => {
|
|
if (hoverStartB) { hoverTimeB += Date.now() - hoverStartB; hoverStartB = null; }
|
|
});
|
|
|
|
// ── Dimension breakdown ──
|
|
const DEFAULT_DIMS = [
|
|
{ id: 'code_quality', name: 'Code Quality' },
|
|
{ id: 'performance', name: 'Performance' },
|
|
{ id: 'error_handling', name: 'Error Handling' },
|
|
{ id: 'documentation', name: 'Documentation' },
|
|
{ id: 'creativity', name: 'Creativity' },
|
|
];
|
|
const dims = (ctx.dimensions && ctx.dimensions.length > 0) ? ctx.dimensions : DEFAULT_DIMS;
|
|
const dimPicks = {};
|
|
|
|
const dimBreakdown = document.getElementById('dimBreakdown');
|
|
dims.forEach(dim => {
|
|
const row = document.createElement('div');
|
|
row.className = 'dim-row';
|
|
row.innerHTML = `
|
|
<div class="dim-name">${dim.name}</div>
|
|
<div class="dim-pick">
|
|
<button class="dim-pick-btn a-pick" data-dim="${dim.id}" data-val="A">A</button>
|
|
<button class="dim-pick-btn tie-pick" data-dim="${dim.id}" data-val="tie">=</button>
|
|
<button class="dim-pick-btn b-pick" data-dim="${dim.id}" data-val="B">B</button>
|
|
</div>
|
|
`;
|
|
dimBreakdown.appendChild(row);
|
|
});
|
|
|
|
document.getElementById('dimToggle').addEventListener('click', () => {
|
|
dimBreakdown.classList.toggle('open');
|
|
document.getElementById('dimToggle').textContent = dimBreakdown.classList.contains('open')
|
|
? '▴ Per-dimension breakdown'
|
|
: '▾ Per-dimension breakdown';
|
|
});
|
|
|
|
dimBreakdown.addEventListener('click', (e) => {
|
|
const btn = e.target.closest('.dim-pick-btn');
|
|
if (!btn) return;
|
|
const dimId = btn.dataset.dim;
|
|
const val = btn.dataset.val;
|
|
dimPicks[dimId] = val;
|
|
|
|
// Update visuals for this row
|
|
btn.closest('.dim-pick').querySelectorAll('.dim-pick-btn').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
});
|
|
|
|
// ── State ──
|
|
let selectedWinner = null;
|
|
let selectedWhys = new Set();
|
|
let interactions = 0;
|
|
let firstInteraction = null;
|
|
|
|
function track() { interactions++; if (!firstInteraction) firstInteraction = Date.now(); }
|
|
|
|
// ── Click contender as shortcut ──
|
|
document.getElementById('contenderA').addEventListener('click', () => pickWinner('A'));
|
|
document.getElementById('contenderB').addEventListener('click', () => pickWinner('B'));
|
|
|
|
// ── Winner buttons ──
|
|
document.querySelectorAll('.win-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => pickWinner(btn.dataset.winner));
|
|
});
|
|
|
|
function pickWinner(winner) {
|
|
track();
|
|
selectedWinner = winner;
|
|
|
|
// Visual states
|
|
document.querySelectorAll('.win-btn').forEach(b => b.classList.remove('selected'));
|
|
const btnMap = { A: 'btnA', B: 'btnB', tie: 'btnTie' };
|
|
document.getElementById(btnMap[winner]).classList.add('selected');
|
|
|
|
const cA = document.getElementById('contenderA');
|
|
const cB = document.getElementById('contenderB');
|
|
cA.classList.remove('winner', 'loser');
|
|
cB.classList.remove('winner', 'loser');
|
|
|
|
if (winner === 'A') {
|
|
cA.classList.add('winner');
|
|
cB.classList.add('loser');
|
|
} else if (winner === 'B') {
|
|
cB.classList.add('winner');
|
|
cA.classList.add('loser');
|
|
}
|
|
|
|
document.getElementById('vsCircle').textContent = winner === 'tie' ? '🤝' : '🏆';
|
|
document.getElementById('vsCircle').classList.add('decided');
|
|
|
|
// Show why section
|
|
document.getElementById('whySection').classList.add('visible');
|
|
document.getElementById('submitRow').classList.add('visible');
|
|
}
|
|
|
|
// ── Why chips ──
|
|
document.querySelectorAll('.why-chip').forEach(chip => {
|
|
chip.addEventListener('click', () => {
|
|
track();
|
|
const w = chip.dataset.why;
|
|
if (selectedWhys.has(w)) { selectedWhys.delete(w); chip.classList.remove('active'); }
|
|
else { selectedWhys.add(w); chip.classList.add('active'); }
|
|
});
|
|
});
|
|
|
|
// ── Submit ──
|
|
document.getElementById('submitBtn').addEventListener('click', () => {
|
|
if (!selectedWinner) return;
|
|
track();
|
|
|
|
// Finalize hover times
|
|
if (hoverStartA) { hoverTimeA += Date.now() - hoverStartA; }
|
|
if (hoverStartB) { hoverTimeB += Date.now() - hoverStartB; }
|
|
|
|
const whyText = document.getElementById('whyInput').value.trim();
|
|
|
|
const perDimension = Object.keys(dimPicks).length > 0
|
|
? Object.entries(dimPicks).map(([dim, winner]) => ({
|
|
dimension: dim,
|
|
winner: winner === 'tie' ? 'tie' : winner,
|
|
}))
|
|
: undefined;
|
|
|
|
const prefMap = { A: 'A', B: 'B', tie: 'neither' };
|
|
|
|
const payload = {
|
|
type: 'factory_modal_response',
|
|
modalType,
|
|
modalVersion,
|
|
pipelineId: ctx.pipelineId || 'unknown',
|
|
itemId: ctx.itemId || 'unknown',
|
|
sessionId: ctx.sessionId || 'sess_' + Date.now(),
|
|
timestamp: new Date().toISOString(),
|
|
responseTimeMs: Date.now() - startTime,
|
|
feedback: {
|
|
comparison: {
|
|
preferred: prefMap[selectedWinner],
|
|
reason: whyText || undefined,
|
|
preferenceStrength: selectedWhys.size >= 3 ? 'strong' : selectedWhys.size >= 1 ? 'moderate' : 'slight',
|
|
winningFactors: Array.from(selectedWhys),
|
|
perDimension,
|
|
timeOnA_ms: Math.round(hoverTimeA),
|
|
timeOnB_ms: Math.round(hoverTimeB),
|
|
},
|
|
},
|
|
meta: {
|
|
timeToFirstInteractionMs: firstInteraction ? firstInteraction - startTime : null,
|
|
timeToDecisionMs: Date.now() - startTime,
|
|
totalInteractions: interactions,
|
|
fieldsModified: ['winner', ...(whyText ? ['whyText'] : []), ...(selectedWhys.size > 0 ? ['whyChips'] : [])],
|
|
deviceType: window.innerWidth < 768 ? 'mobile' : 'desktop',
|
|
viewportSize: { width: window.innerWidth, height: window.innerHeight },
|
|
hoverJourney: [
|
|
{ target: 'contenderA', durationMs: Math.round(hoverTimeA) },
|
|
{ target: 'contenderB', durationMs: Math.round(hoverTimeB) },
|
|
],
|
|
},
|
|
};
|
|
|
|
post(payload);
|
|
|
|
const winnerLabel = selectedWinner === 'A' ? 'Option A wins!' : selectedWinner === 'B' ? 'Option B wins!' : "It's a tie!";
|
|
document.getElementById('doneText').textContent = winnerLabel;
|
|
document.getElementById('doneOverlay').classList.add('visible');
|
|
|
|
setTimeout(() => {
|
|
post({ type: 'factory_modal_close', reason: 'completed' });
|
|
}, 1200);
|
|
});
|
|
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|