510 lines
14 KiB
HTML
510 lines
14 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 — Report Card</title>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
:root {
|
|
--bg: #111;
|
|
--paper: #FFF8E7;
|
|
--paper-dark: #F5EDDA;
|
|
--ink: #2a2a2a;
|
|
--ink-muted: #888;
|
|
--grade-a: #2255CC;
|
|
--grade-b: #22AA55;
|
|
--grade-c: #CCAA00;
|
|
--grade-d: #DD7700;
|
|
--grade-f: #CC2222;
|
|
--radius: 6px;
|
|
}
|
|
|
|
body {
|
|
background: var(--bg);
|
|
font-family: 'Georgia', 'Times New Roman', serif;
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 20px 12px;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* ── Report Card Container ── */
|
|
.report-card {
|
|
background: var(--paper);
|
|
border-radius: 4px;
|
|
width: 100%;
|
|
max-width: 500px;
|
|
padding: 0;
|
|
box-shadow:
|
|
3px 5px 16px rgba(0,0,0,0.45),
|
|
0 0 0 1px rgba(0,0,0,0.08);
|
|
position: relative;
|
|
overflow: hidden;
|
|
color: var(--ink);
|
|
}
|
|
|
|
/* ── Header ── */
|
|
.rc-header {
|
|
background: linear-gradient(135deg, #1a2a4a, #2a3a5a);
|
|
color: #fff;
|
|
padding: 20px 24px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
position: relative;
|
|
}
|
|
.rc-header::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 3px;
|
|
background: linear-gradient(90deg, var(--grade-a), var(--grade-b), var(--grade-c), var(--grade-d), var(--grade-f));
|
|
}
|
|
.rc-header .school {
|
|
font-size: 10px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 2.5px;
|
|
opacity: 0.7;
|
|
margin-bottom: 4px;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
}
|
|
.rc-header .student-name {
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* ── GPA Circle ── */
|
|
.gpa-circle {
|
|
width: 64px;
|
|
height: 64px;
|
|
border-radius: 50%;
|
|
border: 3px solid rgba(255,255,255,0.3);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
transition: all 0.5s;
|
|
}
|
|
.gpa-circle .gpa-num {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
line-height: 1;
|
|
}
|
|
.gpa-circle .gpa-label {
|
|
font-size: 8px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
opacity: 0.6;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
}
|
|
.gpa-circle.excellent { border-color: var(--grade-a); }
|
|
.gpa-circle.good { border-color: var(--grade-b); }
|
|
.gpa-circle.ok { border-color: var(--grade-c); }
|
|
.gpa-circle.poor { border-color: var(--grade-d); }
|
|
.gpa-circle.failing { border-color: var(--grade-f); }
|
|
|
|
/* ── Subjects Section ── */
|
|
.subjects {
|
|
padding: 20px 24px 16px;
|
|
}
|
|
.subjects-label {
|
|
font-size: 10px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 2px;
|
|
color: var(--ink-muted);
|
|
margin-bottom: 14px;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
}
|
|
|
|
.subject-row {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 10px 0;
|
|
border-bottom: 1px solid #e8dcc8;
|
|
gap: 12px;
|
|
}
|
|
.subject-row:last-child { border-bottom: none; }
|
|
|
|
.subject-name {
|
|
flex: 1;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
min-width: 0;
|
|
}
|
|
|
|
.grade-bubbles {
|
|
display: flex;
|
|
gap: 6px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* ── Grade Bubbles ── */
|
|
.bubble {
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 50%;
|
|
border: 2.5px solid #bbb;
|
|
background: transparent;
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
color: #999;
|
|
cursor: pointer;
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: all 0.2s;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
.bubble::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: 50%;
|
|
background: var(--fill-color);
|
|
transform: scale(0);
|
|
transition: transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
z-index: 0;
|
|
}
|
|
.bubble span { position: relative; z-index: 1; }
|
|
|
|
.bubble:hover {
|
|
border-color: #888;
|
|
transform: scale(1.08);
|
|
}
|
|
.bubble.filled::before {
|
|
transform: scale(1);
|
|
}
|
|
.bubble.filled {
|
|
border-color: var(--fill-color);
|
|
color: #fff;
|
|
}
|
|
|
|
.bubble[data-grade="A"] { --fill-color: var(--grade-a); }
|
|
.bubble[data-grade="B"] { --fill-color: var(--grade-b); }
|
|
.bubble[data-grade="C"] { --fill-color: var(--grade-c); }
|
|
.bubble[data-grade="D"] { --fill-color: var(--grade-d); }
|
|
.bubble[data-grade="F"] { --fill-color: var(--grade-f); }
|
|
|
|
/* ── Teacher's Comments ── */
|
|
.comments-section {
|
|
padding: 0 24px 20px;
|
|
}
|
|
.comments-label {
|
|
font-size: 10px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 2px;
|
|
color: var(--ink-muted);
|
|
margin-bottom: 10px;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
}
|
|
.teachers-comments {
|
|
width: 100%;
|
|
min-height: 80px;
|
|
background: transparent;
|
|
border: none;
|
|
border-bottom: 2px solid #e8dcc8;
|
|
font-family: 'Georgia', serif;
|
|
font-size: 14px;
|
|
color: var(--ink);
|
|
line-height: 28px;
|
|
padding: 4px 0;
|
|
resize: vertical;
|
|
outline: none;
|
|
background-image: repeating-linear-gradient(
|
|
transparent,
|
|
transparent 27px,
|
|
#d4c8b4 27px,
|
|
#d4c8b4 28px
|
|
);
|
|
}
|
|
.teachers-comments::placeholder { color: #bbb; font-style: italic; }
|
|
|
|
/* ── Submit ── */
|
|
.submit-section {
|
|
padding: 16px 24px 24px;
|
|
background: var(--paper-dark);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
border-top: 1px solid #e0d5c0;
|
|
}
|
|
.grade-progress {
|
|
font-size: 12px;
|
|
color: var(--ink-muted);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
}
|
|
.submit-btn {
|
|
padding: 12px 28px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
background: linear-gradient(135deg, #2a3a5a, #1a2a4a);
|
|
color: #fff;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
transition: all 0.25s;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
.submit-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.3); }
|
|
.submit-btn:active { transform: scale(0.97); }
|
|
.submit-btn:disabled { opacity: 0.35; pointer-events: none; }
|
|
|
|
/* ── 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: 8px; }
|
|
.done-overlay .done-text { font-size: 18px; color: #aaa; font-family: -apple-system, sans-serif; }
|
|
.done-overlay .done-gpa { font-size: 40px; font-weight: 800; color: #fff; margin-top: 4px; font-family: -apple-system, sans-serif; }
|
|
|
|
/* ── Grade tooltip ── */
|
|
.grade-hint {
|
|
position: fixed;
|
|
background: #333;
|
|
color: #fff;
|
|
padding: 4px 10px;
|
|
border-radius: 6px;
|
|
font-size: 11px;
|
|
font-family: -apple-system, sans-serif;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.15s;
|
|
z-index: 50;
|
|
white-space: nowrap;
|
|
}
|
|
.grade-hint.show { opacity: 1; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="report-card" id="card">
|
|
<div class="rc-header">
|
|
<div>
|
|
<div class="school">GooseFactory Academy</div>
|
|
<div class="student-name" id="studentName">MCP Server</div>
|
|
</div>
|
|
<div class="gpa-circle" id="gpaCircle">
|
|
<div class="gpa-num" id="gpaNum">—</div>
|
|
<div class="gpa-label">GPA</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="subjects" id="subjects">
|
|
<div class="subjects-label">Subject Grades</div>
|
|
<!-- rows injected by JS -->
|
|
</div>
|
|
|
|
<div class="comments-section">
|
|
<div class="comments-label">Teacher's Comments</div>
|
|
<textarea class="teachers-comments" id="comments" placeholder="Add notes about this review…" rows="3"></textarea>
|
|
</div>
|
|
|
|
<div class="submit-section">
|
|
<div class="grade-progress" id="gradeProgress">0 / 6 graded</div>
|
|
<button class="submit-btn" id="submitBtn" disabled>Sign & Submit ✍️</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="done-overlay" id="doneOverlay">
|
|
<div class="done-emoji">📋</div>
|
|
<div class="done-text">Report Card Filed</div>
|
|
<div class="done-gpa" id="doneGpa">4.0</div>
|
|
</div>
|
|
|
|
<div class="grade-hint" id="gradeHint"></div>
|
|
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
const ctx = window.__FACTORY_CONTEXT__ || {};
|
|
const modalType = 'report-card';
|
|
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 });
|
|
|
|
// ── Config ──
|
|
const DEFAULT_DIMENSIONS = [
|
|
{ id: 'code_quality', name: 'Code Quality' },
|
|
{ id: 'documentation', name: 'Documentation' },
|
|
{ id: 'error_handling', name: 'Error Handling' },
|
|
{ id: 'performance', name: 'Performance' },
|
|
{ id: 'creativity', name: 'Creativity' },
|
|
{ id: 'completeness', name: 'Completeness' },
|
|
];
|
|
|
|
const dimensions = (ctx.dimensions && ctx.dimensions.length > 0)
|
|
? ctx.dimensions
|
|
: DEFAULT_DIMENSIONS;
|
|
|
|
const GRADES = ['A', 'B', 'C', 'D', 'F'];
|
|
const GPA_MAP = { A: 4.0, B: 3.0, C: 2.0, D: 1.0, F: 0.0 };
|
|
const GRADE_HINTS = {
|
|
A: 'Exceptional',
|
|
B: 'Good',
|
|
C: 'Acceptable',
|
|
D: 'Below Standard',
|
|
F: 'Failing',
|
|
};
|
|
|
|
document.getElementById('studentName').textContent = ctx.itemName || ctx.pipelineName || 'MCP Server';
|
|
|
|
// ── State ──
|
|
const grades = {}; // dimId → grade letter
|
|
const gradeChanges = []; // track every change
|
|
let interactionCount = 0;
|
|
let firstInteraction = null;
|
|
|
|
function track() { interactionCount++; if (!firstInteraction) firstInteraction = Date.now(); }
|
|
|
|
// ── Build Subject Rows ──
|
|
const subjectsEl = document.getElementById('subjects');
|
|
dimensions.forEach(dim => {
|
|
const row = document.createElement('div');
|
|
row.className = 'subject-row';
|
|
row.innerHTML = `
|
|
<div class="subject-name">${dim.name}</div>
|
|
<div class="grade-bubbles" data-dim="${dim.id}">
|
|
${GRADES.map(g => `<button class="bubble" data-grade="${g}" data-dim="${dim.id}" aria-label="${g} grade"><span>${g}</span></button>`).join('')}
|
|
</div>
|
|
`;
|
|
subjectsEl.appendChild(row);
|
|
});
|
|
|
|
// ── Grade Selection ──
|
|
const hint = document.getElementById('gradeHint');
|
|
|
|
document.querySelectorAll('.bubble').forEach(bubble => {
|
|
bubble.addEventListener('click', () => {
|
|
track();
|
|
const dimId = bubble.dataset.dim;
|
|
const grade = bubble.dataset.grade;
|
|
const prev = grades[dimId];
|
|
|
|
if (prev && prev !== grade) {
|
|
gradeChanges.push({ dim: dimId, from: prev, to: grade, at: Date.now() - startTime });
|
|
}
|
|
|
|
grades[dimId] = grade;
|
|
|
|
// Update visuals
|
|
document.querySelectorAll(`.bubble[data-dim="${dimId}"]`).forEach(b => {
|
|
b.classList.remove('filled');
|
|
if (b.dataset.grade === grade) b.classList.add('filled');
|
|
});
|
|
|
|
updateGPA();
|
|
updateProgress();
|
|
});
|
|
|
|
// Tooltip
|
|
bubble.addEventListener('mouseenter', (e) => {
|
|
hint.textContent = GRADE_HINTS[bubble.dataset.grade];
|
|
hint.style.left = e.clientX + 8 + 'px';
|
|
hint.style.top = e.clientY - 30 + 'px';
|
|
hint.classList.add('show');
|
|
});
|
|
bubble.addEventListener('mouseleave', () => hint.classList.remove('show'));
|
|
});
|
|
|
|
// ── GPA ──
|
|
const gpaNum = document.getElementById('gpaNum');
|
|
const gpaCircle = document.getElementById('gpaCircle');
|
|
|
|
function updateGPA() {
|
|
const graded = Object.values(grades);
|
|
if (graded.length === 0) { gpaNum.textContent = '—'; return; }
|
|
const sum = graded.reduce((s, g) => s + GPA_MAP[g], 0);
|
|
const gpa = sum / graded.length;
|
|
gpaNum.textContent = gpa.toFixed(1);
|
|
|
|
gpaCircle.className = 'gpa-circle ' + (
|
|
gpa >= 3.7 ? 'excellent' :
|
|
gpa >= 2.7 ? 'good' :
|
|
gpa >= 1.7 ? 'ok' :
|
|
gpa >= 0.7 ? 'poor' : 'failing'
|
|
);
|
|
}
|
|
|
|
function updateProgress() {
|
|
const filled = Object.keys(grades).length;
|
|
const total = dimensions.length;
|
|
document.getElementById('gradeProgress').textContent = `${filled} / ${total} graded`;
|
|
document.getElementById('submitBtn').disabled = (filled < total);
|
|
}
|
|
|
|
// ── Submit ──
|
|
document.getElementById('submitBtn').addEventListener('click', () => {
|
|
if (document.getElementById('submitBtn').disabled) return;
|
|
track();
|
|
|
|
const graded = Object.values(grades);
|
|
const gpa = graded.reduce((s, g) => s + GPA_MAP[g], 0) / graded.length;
|
|
const comments = document.getElementById('comments').value.trim();
|
|
|
|
const scores = dimensions.map(dim => ({
|
|
dimension: dim.id,
|
|
score: GPA_MAP[grades[dim.id]] / 4 * 10, // Convert to 1-10 scale
|
|
grade: grades[dim.id],
|
|
weight: dim.weight || 1,
|
|
}));
|
|
|
|
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: {
|
|
scores,
|
|
freeText: comments ? { generalNotes: comments } : undefined,
|
|
custom: {
|
|
grades: { ...grades },
|
|
gpa: parseFloat(gpa.toFixed(2)),
|
|
gradeChanges,
|
|
},
|
|
},
|
|
meta: {
|
|
timeToFirstInteractionMs: firstInteraction ? firstInteraction - startTime : null,
|
|
timeToDecisionMs: Date.now() - startTime,
|
|
totalInteractions: interactionCount,
|
|
fieldsModified: [...Object.keys(grades).map(d => 'grade_' + d), ...(comments ? ['comments'] : [])],
|
|
deviceType: window.innerWidth < 768 ? 'mobile' : 'desktop',
|
|
viewportSize: { width: window.innerWidth, height: window.innerHeight },
|
|
},
|
|
};
|
|
|
|
post(payload);
|
|
|
|
// Done animation
|
|
document.getElementById('doneGpa').textContent = gpa.toFixed(1);
|
|
document.getElementById('doneOverlay').classList.add('visible');
|
|
|
|
setTimeout(() => {
|
|
post({ type: 'factory_modal_close', reason: 'completed' });
|
|
}, 1400);
|
|
});
|
|
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|