Files
lucidia-math/templates/learning_history.html
Alexa Louise dbf28f72b5 feat: Sync latest templates from blackroad-sandbox
 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-12 01:38:20 -06:00

516 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Learning History - BlackRoad OS (Σ)</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root {
--bg-primary: #020308;
--bg-secondary: rgba(6, 10, 30, 0.92);
--text-primary: #f5f5ff;
--text-muted: rgba(245, 245, 255, 0.7);
--accent-cyan: #00e5ff;
--accent-green: #1af59d;
--accent-yellow: #ffc400;
--accent-purple: #a855f7;
--accent-orange: #ff9d00;
--border-subtle: rgba(255, 255, 255, 0.1);
}
* {
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
background: radial-gradient(circle at top, #050816 0, #020308 45%, #000000 100%);
color: var(--text-primary);
margin: 0;
padding: 0;
min-height: 100vh;
}
.page {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px 80px;
}
h1 {
margin: 0 0 8px;
font-size: 1.6rem;
display: flex;
align-items: center;
gap: 12px;
}
h1 .sigma {
color: var(--accent-purple);
font-size: 2rem;
}
h2 {
margin: 0 0 12px;
font-size: 1rem;
color: var(--text-muted);
}
.muted {
opacity: 0.7;
font-size: 0.85rem;
}
.mono {
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.8rem;
}
.panel {
border-radius: 16px;
border: 1px solid var(--border-subtle);
background: var(--bg-secondary);
padding: 16px 18px;
margin-top: 16px;
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 16px;
}
.stat-card {
text-align: center;
}
.stat-label {
font-size: 0.75rem;
color: var(--text-muted);
margin-bottom: 4px;
}
.stat-value {
font-family: "JetBrains Mono", monospace;
font-size: 1.8rem;
font-weight: 600;
}
.stat-value.xp {
color: var(--accent-yellow);
}
.stat-value.lessons {
color: var(--accent-green);
}
.stat-value.streak {
color: var(--accent-orange);
}
/* Rank Card */
.rank-card {
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
background: rgba(168, 85, 247, 0.08);
border: 1px solid rgba(168, 85, 247, 0.3);
border-radius: 12px;
margin-top: 12px;
}
.rank-badge {
width: 56px;
height: 56px;
border-radius: 50%;
background: linear-gradient(135deg, var(--accent-purple), var(--accent-cyan));
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: bold;
color: white;
flex-shrink: 0;
}
.rank-info {
flex: 1;
}
.rank-name {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 4px;
}
.rank-progress-container {
display: flex;
align-items: center;
gap: 10px;
}
.rank-progress-bar {
flex: 1;
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow: hidden;
}
.rank-progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent-purple), var(--accent-cyan));
border-radius: 4px;
transition: width 0.3s ease;
}
.rank-progress-text {
font-size: 0.75rem;
color: var(--text-muted);
white-space: nowrap;
}
/* Event List */
.event-row {
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
padding: 12px 0;
}
.event-row:last-child {
border-bottom: none;
}
.event-main {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
}
.event-meta {
text-align: right;
font-size: 0.75rem;
color: var(--text-muted);
}
.tag {
display: inline-flex;
align-items: center;
padding: 3px 10px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.2);
font-size: 0.75rem;
margin-right: 6px;
margin-bottom: 4px;
}
.tag.lesson {
border-color: rgba(26, 245, 157, 0.6);
background: rgba(26, 245, 157, 0.08);
}
.tag.quiz {
border-color: rgba(0, 180, 255, 0.7);
background: rgba(0, 180, 255, 0.08);
}
.tag.graded {
border-color: rgba(255, 196, 0, 0.8);
background: rgba(255, 196, 0, 0.08);
}
.tag.rank-up {
border-color: rgba(168, 85, 247, 0.8);
background: rgba(168, 85, 247, 0.15);
font-weight: 600;
}
.rank-up-card {
background: linear-gradient(135deg, rgba(168, 85, 247, 0.12), rgba(0, 229, 255, 0.08));
border: 1px solid rgba(168, 85, 247, 0.4);
border-radius: 12px;
padding: 12px 16px;
margin-top: 8px;
}
.rank-up-title {
font-size: 1.1rem;
font-weight: 600;
color: var(--accent-purple);
margin-bottom: 4px;
}
.rank-arrow {
color: var(--accent-cyan);
margin: 0 8px;
}
.score-pill {
padding: 2px 10px;
border-radius: 999px;
background: rgba(255, 196, 0, 0.15);
border: 1px solid rgba(255, 196, 0, 0.5);
font-size: 0.75rem;
font-weight: 600;
}
.score-pill.perfect {
background: rgba(26, 245, 157, 0.15);
border-color: rgba(26, 245, 157, 0.5);
color: var(--accent-green);
}
.small {
font-size: 0.78rem;
}
.results-list {
margin-top: 8px;
padding-left: 12px;
}
.result-item {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 4px;
}
.result-icon {
font-size: 0.9rem;
}
.result-icon.correct {
color: var(--accent-green);
}
.result-icon.incorrect {
color: #ff6b6b;
}
/* Links */
a {
color: #66b2ff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* Empty state */
.empty-state {
text-align: center;
padding: 32px 16px;
color: var(--text-muted);
}
.empty-state .icon {
font-size: 3rem;
margin-bottom: 12px;
opacity: 0.5;
}
/* Tips panel */
.tips-list {
margin: 8px 0 0 0;
padding-left: 20px;
}
.tips-list li {
margin-bottom: 6px;
}
</style>
</head>
<body>
<div class="page">
<div style="margin-bottom: 24px;">
<a href="/" style="color: #00e5ff; text-decoration: none; font-size: 0.9rem;">&larr; Dashboard</a>
</div>
<h1><span class="sigma">Σ</span> Learning History</h1>
<p class="muted">
Actor: <span class="mono">{{ actor }}</span><br />
Tracking: <span class="mono">lesson.completed</span>,
<span class="mono">quiz.submitted</span>,
<span class="mono">quiz.graded</span>
</p>
{% if stats_error %}
<div class="panel">
<div class="muted small">Error loading stats: {{ stats_error }}</div>
</div>
{% else %}
<!-- Stats Summary -->
<div class="panel">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">Total XP</div>
<div class="stat-value xp">{{ stats.xp or 0 }}</div>
</div>
<div class="stat-card">
<div class="stat-label">Lessons</div>
<div class="stat-value lessons">{{ stats.lessons_completed or 0 }}</div>
</div>
<div class="stat-card">
<div class="stat-label">Quizzes</div>
<div class="stat-value">{{ stats.quizzes_graded or 0 }}</div>
</div>
<div class="stat-card">
<div class="stat-label">Current Streak</div>
<div class="stat-value streak">
{{ stats.current_streak_days or 0 }}
<span class="small">day{{ 's' if (stats.current_streak_days or 0) != 1 else '' }}</span>
</div>
</div>
<div class="stat-card">
<div class="stat-label">Best Streak</div>
<div class="stat-value">
{{ stats.longest_streak_days or 0 }}
<span class="small">day{{ 's' if (stats.longest_streak_days or 0) != 1 else '' }}</span>
</div>
</div>
<div class="stat-card">
<div class="stat-label">Last Active</div>
<div class="mono small" style="margin-top: 8px;">
{{ stats.last_active_date or "Never" }}
</div>
</div>
</div>
{% if stats.rank %}
<!-- Rank Card -->
<div class="rank-card">
<div class="rank-badge">
{{ stats.rank.rank_index + 1 }}
</div>
<div class="rank-info">
<div class="rank-name">{{ stats.rank.rank_name }}</div>
{% if stats.rank.next_rank_name %}
<div class="rank-progress-container">
<div class="rank-progress-bar">
<div class="rank-progress-fill" style="width: {{ (stats.rank.progress * 100)|int }}%;"></div>
</div>
<div class="rank-progress-text">
{{ stats.rank.xp }} / {{ stats.rank.xp_for_next }} XP to {{ stats.rank.next_rank_name }}
</div>
</div>
{% else %}
<div class="rank-progress-text">Max rank achieved!</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
{% endif %}
{% if events_error %}
<div class="panel">
<div class="muted small">Error loading events: {{ events_error }}</div>
</div>
{% endif %}
<!-- Events List -->
<div class="panel">
<h2>Activity Log</h2>
{% if not events %}
<div class="empty-state">
<div class="icon">📚</div>
<p>No learning events recorded yet.</p>
<p class="small">Complete a Math Lab lesson to get started!</p>
</div>
{% else %}
{% for e in events %}
<div class="event-row">
<div class="event-main">
<div>
{% if e.event_type == "lesson.completed" %}
<span class="tag lesson">Lesson {{ e.lesson }} completed</span>
{% elif e.event_type == "quiz.submitted" %}
<span class="tag quiz">Quiz submitted (L{{ e.lesson }})</span>
{% elif e.event_type == "quiz.graded" %}
<span class="tag graded">Quiz graded (L{{ e.lesson }})</span>
{% elif e.event_type == "rank.changed" %}
<span class="tag rank-up">🎖️ Rank Up!</span>
{% else %}
<span class="tag">{{ e.event_type }}</span>
{% endif %}
{% if e.event_type == "lesson.completed" %}
<div class="small muted">
Track: <span class="mono">{{ e.track }}</span>
{% if e.metadata and e.metadata.title %}
- {{ e.metadata.title }}
{% endif %}
</div>
{% elif e.event_type == "quiz.graded" %}
<div class="small">
Score:
{% if e.score is not none %}
{% set score_pct = (e.score * 100)|int %}
<span class="score-pill {{ 'perfect' if score_pct == 100 else '' }}">
{{ score_pct }}%
</span>
{% if score_pct == 100 %}
<span style="margin-left: 4px;">Perfect!</span>
{% endif %}
{% else %}
<span class="muted">n/a</span>
{% endif %}
</div>
{% elif e.event_type == "rank.changed" %}
<div class="rank-up-card">
<div class="rank-up-title">
{{ e.old_rank or "New" }}
<span class="rank-arrow"></span>
{{ e.new_rank }}
</div>
<div class="small muted" style="margin-top: 4px;">
XP: <span class="mono">{{ e.xp or 0 }}</span>
</div>
</div>
{% endif %}
</div>
<div class="event-meta">
<div class="mono">
{{ (e.timestamp or "")[:19] }}
</div>
{% if e.node_id %}
<div class="small">node: <span class="mono">{{ e.node_id }}</span></div>
{% endif %}
</div>
</div>
{% if e.event_type == "quiz.graded" and e.results %}
<div class="results-list">
{% for r in e.results %}
<div class="result-item mono small">
<span class="result-icon {{ 'correct' if r.correct else 'incorrect' }}">
{{ "✓" if r.correct else "✗" }}
</span>
<span>{{ r.id }}: {{ r.feedback[:60] }}{{ '...' if r.feedback|length > 60 else '' }}</span>
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
{% endif %}
</div>
<!-- Tips -->
<div class="panel">
<h2>How to Earn XP</h2>
<ul class="tips-list small">
<li><strong>+10 XP</strong> - Complete a Math Lab lesson</li>
<li><strong>+2 XP</strong> - Submit a quiz</li>
<li><strong>+0-20 XP</strong> - Quiz graded (score × 20)</li>
<li>Keep a streak going by learning every day!</li>
</ul>
</div>
</div>
</body>
</html>