1
0

vault backup: 2025-12-22 12:22:20
All checks were successful
Deploy Quartz site to GitHub Pages / build (push) Successful in 2m12s

This commit is contained in:
2025-12-22 12:22:20 +01:00
parent d1d89e5442
commit 5169a67966
23 changed files with 12058 additions and 288 deletions

View File

@@ -3,19 +3,170 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quiz</title>
<title>Medical Quiz</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style>
body { font-family: system-ui, -apple-system, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.question { background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0; }
.option { padding: 10px; margin: 5px 0; cursor: pointer; border: 2px solid #ddd; border-radius: 4px; }
.option:hover { background: #e9e9e9; }
.progress { background: #ddd; height: 20px; border-radius: 10px; margin: 20px 0; }
.progress-bar { background: #4CAF50; height: 100%; border-radius: 10px; transition: width 0.3s; }
:root {
--primary: #6366f1;
--primary-hover: #4f46e5;
--bg: #f8fafc;
--card-bg: rgba(255, 255, 255, 0.8);
--text-main: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--glass-bg: rgba(255, 255, 255, 0.7);
--glass-border: rgba(255, 255, 255, 0.3);
}
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background-color: var(--bg);
color: var(--text-main);
line-height: 1.5;
margin: 0;
padding: 0;
min-height: 100vh;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 2rem;
}
h1, h2, h3 {
font-weight: 700;
margin-top: 0;
}
.glass-card {
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: 1rem;
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
padding: 2rem;
margin-bottom: 2rem;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: none;
text-decoration: none;
font-size: 0.95rem;
}
.btn-primary {
background-color: var(--primary);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-hover);
transform: translateY(-1px);
}
.btn-secondary {
background-color: white;
color: var(--text-main);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background-color: #f1f5f9;
}
.btn-danger {
background-color: #ef4444;
color: white;
}
.btn-danger:hover {
background-color: #dc2626;
}
.progress-container {
background: var(--border);
height: 0.5rem;
border-radius: 1rem;
overflow: hidden;
margin: 1rem 0;
}
.progress-bar {
background: var(--primary);
height: 100%;
transition: width 0.3s ease;
}
/* Forms */
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-muted);
}
select, input, textarea {
width: 100%;
padding: 0.625rem;
border-radius: 0.5rem;
border: 1px solid var(--border);
background-color: white;
font-family: inherit;
font-size: 0.95rem;
}
/* Grid */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.session-card {
display: flex;
flex-direction: column;
gap: 1rem;
}
.session-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.session-title {
font-weight: 600;
font-size: 1.1rem;
}
.session-meta {
font-size: 0.85rem;
color: var(--text-muted);
}
</style>
</head>
<body>
{% block content %}{% endblock %}
<div class="container">
{% block content %}{% endblock %}
</div>
</body>
</html>

View File

@@ -1,53 +1,90 @@
{% extends "base.html" %}
{% block content %}
<style>
.filter-section {
margin-bottom: 20px;
}
<div class="header" style="margin-bottom: 3rem; display: flex; justify-content: space-between; align-items: center;">
<div>
<h1 style="font-size: 2.5rem; margin-bottom: 0.5rem;">Välkommen</h1>
<p style="color: var(--text-muted);">Här kan du hantera dina medicinska quiz.</p>
</div>
<div style="text-align: right;">
<div style="font-weight: 600; font-size: 1.25rem;">{{ answered_count }} / {{ total_questions }}</div>
<div style="font-size: 0.875rem; color: var(--text-muted);">Frågor besvarade</div>
<div class="progress-container" style="width: 150px; margin-top: 0.5rem;">
<div class="progress-bar"
style="width: {% if total_questions > 0 %}{{ answered_count|add:0|floatformat:2 }}{% else %}0{% endif %}%">
</div>
</div>
</div>
</div>
.tag-chip {
display: inline-block;
padding: 5px 12px;
margin: 4px;
border-radius: 16px;
background: #e0e0e0;
text-decoration: none;
color: #333;
font-size: 14px;
transition: background 0.2s;
}
<h2 style="margin-bottom: 1.5rem;">Aktiva Quiz</h2>
{% if active_sessions %}
<div class="grid">
{% for session in active_sessions %}
<div class="glass-card session-card" id="session-{{ session.id }}">
<div class="session-header">
<div>
<div class="session-title">
{% if session.course %}{{ session.course.name }}{% else %}Blandat Quiz{% endif %}
</div>
<div class="session-meta">
Startat {{ session.created_at|date:"Y-m-d H:i" }}
</div>
</div>
<form action="{% url 'close_quiz' session.id %}" method="post" hx-post="{% url 'close_quiz' session.id %}"
hx-target="#session-{{ session.id }}" hx-swap="outerHTML">
{% csrf_token %}
<button type="submit" class="btn btn-secondary"
style="padding: 0.25rem 0.5rem; color: #ef4444;">Stäng</button>
</form>
</div>
.tag-chip.active {
background: #4CAF50;
color: white;
}
{% if session.tags.exists %}
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
{% for tag in session.tags.all %}
<span
style="font-size: 0.75rem; background: #e0e7ff; color: #4338ca; padding: 0.25rem 0.5rem; border-radius: 1rem;">{{
tag.name }}</span>
{% endfor %}
</div>
{% endif %}
.tag-chip:hover {
background: #d5d5d5;
}
.tag-chip.active:hover {
background: #45a049;
}
</style>
<h1>Quiz Application</h1>
<div class="filter-section">
<a href="{% url 'create_quiz' %}" class="tag-chip" style="background: #2196F3; color: white;">+ New Quiz</a>
<a href="?tag=" class="tag-chip {% if not current_tag %}active{% endif %}">All</a>
{% for tag in tags %}
<a href="?tag={{ tag.slug }}" class="tag-chip {% if current_tag == tag.slug %}active{% endif %}">
{{ tag.name }}
</a>
<div style="margin-top: auto;">
<a href="{% url 'quiz_mode' session.id %}" class="btn btn-primary" style="width: 100%; text-align: center;">
Fortsätt Quiz
</a>
</div>
</div>
{% endfor %}
</div>
<div class="progress">
<div class="progress-bar"
style="width: {% if total_questions > 0 %}{{ answered_count|floatformat:0 }}{% else %}0{% endif %}%"></div>
{% else %}
<div class="glass-card" style="text-align: center; color: var(--text-muted); padding: 3rem;">
Inga aktiva quiz. Starta ett nytt nedan!
</div>
{% endif %}
<div style="margin-top: 4rem;">
<h2 style="margin-bottom: 1.5rem;">Starta Nytt Quiz</h2>
<div class="glass-card">
<form method="post" action="{% url 'create_quiz' %}">
{% csrf_token %}
<div class="grid">
<div class="form-group">
<label for="{{ form.course.id_for_label }}">Kurs</label>
{{ form.course }}
</div>
<div class="form-group">
<label for="{{ form.tags.id_for_label }}">Taggar</label>
{{ form.tags }}
<small style="color: var(--text-muted); margin-top: 0.25rem; display: block;">Håll ner Ctrl/Cmd för
att välja flera.</small>
</div>
</div>
<div style="margin-top: 1rem;">
<button type="submit" class="btn btn-primary" style="padding-left: 2rem; padding-right: 2rem;">Skapa
Quiz</button>
</div>
</form>
</div>
</div>
<p>Besvarade frågor: {{ answered_count }} / {{ total_questions }}</p>
<div id="quiz-container" hx-get="{% url 'next_question' %}" hx-trigger="load"></div>
{% endblock %}

View File

@@ -1,6 +1,13 @@
<div class="question">
<h2>Quiz Completed!</h2>
<p>Du har besvarat alla frågor.</p>
<a href="{% url 'stats' %}">Se dina resultat</a>
</div>
<div style="text-align: center; padding: 2rem;">
<div style="font-size: 3rem; margin-bottom: 1rem;">🎉</div>
<h2 style="margin-bottom: 1rem;">Quiz Slutfört!</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">Bra jobbat! Du har besvarat alla tillgängliga frågor i
detta urval.</p>
<div style="display: flex; gap: 1rem; justify-content: center;">
<a href="{% url 'stats' %}" class="btn btn-secondary">Se Statistik</a>
<form action="{% url 'close_quiz' session.id %}" method="post">
{% csrf_token %}
<button type="submit" class="btn btn-primary">Avsluta & Gå Tillbaka</button>
</form>
</div>
</div>

View File

@@ -1,14 +1,24 @@
<div class="question">
<h2>{{ question.text }}</h2>
<form hx-post="{% url 'submit_answer' %}" hx-target="#quiz-container">
<div class="quiz-content">
<h3 style="margin-bottom: 1.5rem;">{{ question.text }}</h3>
<form hx-post="{% url 'submit_answer' session.id %}" hx-target="#quiz-container-{{ session.id }}">
{% csrf_token %}
<input type="hidden" name="question_id" value="{{ question.id }}">
{% for option in question.options.all %}
<div class="option" onclick="this.querySelector('input').checked = true; this.closest('form').requestSubmit();">
<input type="radio" name="answer" value="{{ option.letter }}" id="opt_{{ option.letter }}" style="display:none;">
<label for="opt_{{ option.letter }}">{{ option.letter }}. {{ option.text }}</label>
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
{% for option in question.options.all %}
<div class="option"
style="padding: 1rem; border: 1px solid var(--border); border-radius: 0.5rem; cursor: pointer; transition: all 0.2s;"
onmouseover="this.style.backgroundColor='#f1f5f9'" onmouseout="this.style.backgroundColor='transparent'"
onclick="this.querySelector('input').checked = true; this.closest('form').requestSubmit();">
<input type="radio" name="answer" value="{{ option.letter }}"
id="opt_{{ session.id }}_{{ question.id }}_{{ option.letter }}" style="display:none;">
<label style="cursor: pointer; font-weight: 500; color: var(--text-main);"
for="opt_{{ session.id }}_{{ question.id }}_{{ option.letter }}">
<span style="display: inline-block; width: 1.5rem; color: var(--primary); font-weight: 700;">{{
option.letter }}</span>
{{ option.text }}
</label>
</div>
{% endfor %}
</div>
{% endfor %}
</form>
</div>
</div>

View File

@@ -0,0 +1,106 @@
{% if show_answer %}
<div class="answer-feedback {{ 'correct' if is_correct else 'incorrect' }}">
{% if is_correct %}
✓ Rätt svar!
{% else %}
✗ Fel svar. Rätt svar är: {{ question.correct_answer }}
{% endif %}
</div>
{% endif %}
<div class="question-text">{{ question.text }}</div>
<div class="options-container">
{% for option in question.options.all %}
<div class="option-item" id="option-{{ option.letter }}"
onclick="selectOption('{{ option.letter }}', {{ question.id }}, {{ session.id }})">
<span class="option-letter">{{ option.letter }}</span>
<span>{{ option.text }}</span>
</div>
{% endfor %}
</div>
{% if show_answer %}
<div class="difficulty-section">
<div class="difficulty-label">Hur svårt var detta?</div>
<div class="difficulty-buttons">
<button class="difficulty-btn again" onclick="submitDifficulty('again', {{ question.id }}, {{ session.id }})">
<div>Igen</div>
<small style="font-size: 0.75rem; font-weight: 400;">&lt;1m</small>
</button>
<button class="difficulty-btn hard" onclick="submitDifficulty('hard', {{ question.id }}, {{ session.id }})">
<div>Svårt</div>
<small style="font-size: 0.75rem; font-weight: 400;">&lt;6m</small>
</button>
<button class="difficulty-btn good" onclick="submitDifficulty('good', {{ question.id }}, {{ session.id }})">
<div>Bra</div>
<small style="font-size: 0.75rem; font-weight: 400;">&lt;10m</small>
</button>
<button class="difficulty-btn easy" onclick="submitDifficulty('easy', {{ question.id }}, {{ session.id }})">
<div>Lätt</div>
<small style="font-size: 0.75rem; font-weight: 400;">4d</small>
</button>
</div>
</div>
{% endif %}
<div class="nav-buttons">
<button class="nav-btn" {% if not has_previous %}disabled{% endif %}
onclick="navigateQuestion('previous', {{ session.id }})">
← Föregående
</button>
<button class="nav-btn" {% if not has_next %}disabled{% endif %}
onclick="navigateQuestion('next', {{ session.id }})">
Nästa →
</button>
</div>
<script>
let selectedAnswer = null;
function selectOption(letter, questionId, sessionId) {
if ({{ 'true' if show_answer else 'false' }
}) return; // Don't allow changing answer after submission
selectedAnswer = letter;
// Visual feedback
document.querySelectorAll('.option-item').forEach(opt => {
opt.style.borderColor = 'var(--border)';
opt.style.background = 'white';
});
const selected = document.getElementById('option-' + letter);
selected.style.borderColor = 'var(--primary)';
selected.style.background = '#f0f4ff';
// Submit answer
htmx.ajax('POST', `/submit/${sessionId}/`, {
target: '#quiz-content',
values: {
question_id: questionId,
answer: letter
}
});
}
function submitDifficulty(difficulty, questionId, sessionId) {
htmx.ajax('POST', `/difficulty/${sessionId}/`, {
values: {
question_id: questionId,
difficulty: difficulty
}
});
// Move to next question after a brief delay
setTimeout(() => {
navigateQuestion('next', sessionId);
}, 300);
}
function navigateQuestion(direction, sessionId) {
htmx.ajax('GET', `/quiz/${sessionId}/${direction}/`, {
target: '#quiz-content'
});
}
</script>

View File

@@ -0,0 +1,210 @@
{% extends "base.html" %}
{% block content %}
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.quiz-mode-container {
max-width: 900px;
margin: 2rem auto;
padding: 0 1rem;
}
.quiz-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
color: white;
}
.quiz-card {
background: white;
border-radius: 1.5rem;
padding: 3rem;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
min-height: 400px;
display: flex;
flex-direction: column;
}
.question-text {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 2rem;
line-height: 1.6;
color: var(--text-main);
}
.options-container {
display: flex;
flex-direction: column;
gap: 1rem;
flex-grow: 1;
}
.option-item {
padding: 1.25rem;
border: 2px solid var(--border);
border-radius: 0.75rem;
cursor: pointer;
transition: all 0.2s;
background: white;
}
.option-item:hover {
border-color: var(--primary);
background: #f8f9fb;
transform: translateX(4px);
}
.option-letter {
display: inline-block;
width: 2rem;
height: 2rem;
background: var(--primary);
color: white;
border-radius: 50%;
text-align: center;
line-height: 2rem;
font-weight: 700;
margin-right: 1rem;
}
.difficulty-section {
margin-top: 2rem;
padding-top: 2rem;
border-top: 2px solid var(--border);
}
.difficulty-label {
font-size: 0.875rem;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 1rem;
text-align: center;
}
.difficulty-buttons {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.75rem;
}
.difficulty-btn {
padding: 0.75rem;
border-radius: 0.5rem;
border: 2px solid var(--border);
background: white;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.difficulty-btn.again {
border-color: #ef4444;
color: #ef4444;
}
.difficulty-btn.again:hover {
background: #ef4444;
color: white;
}
.difficulty-btn.hard {
border-color: #f59e0b;
color: #f59e0b;
}
.difficulty-btn.hard:hover {
background: #f59e0b;
color: white;
}
.difficulty-btn.good {
border-color: #10b981;
color: #10b981;
}
.difficulty-btn.good:hover {
background: #10b981;
color: white;
}
.difficulty-btn.easy {
border-color: #6366f1;
color: #6366f1;
}
.difficulty-btn.easy:hover {
background: #6366f1;
color: white;
}
.nav-buttons {
display: flex;
justify-content: space-between;
margin-top: 2rem;
}
.nav-btn {
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
border: none;
background: var(--primary);
color: white;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.nav-btn:hover {
background: var(--primary-hover);
}
.nav-btn:disabled {
background: #cbd5e1;
cursor: not-allowed;
}
.answer-feedback {
padding: 1rem;
border-radius: 0.5rem;
margin-bottom: 1rem;
font-weight: 600;
}
.answer-feedback.correct {
background: #d1fae5;
color: #065f46;
border: 2px solid #10b981;
}
.answer-feedback.incorrect {
background: #fee2e2;
color: #991b1b;
border: 2px solid #ef4444;
}
</style>
<div class="quiz-mode-container">
<div class="quiz-header">
<h1 style="margin: 0;">{{ session.course.name|default:"Quiz" }}</h1>
<a href="{% url 'index' %}" class="btn btn-secondary">← Tillbaka till Dashboard</a>
</div>
<div class="quiz-card" id="quiz-content">
<!-- Content loaded via HTMX -->
</div>
</div>
<script>
// Load first question on page load
document.addEventListener('DOMContentLoaded', function () {
htmx.ajax('GET', '{% url 'quiz_question' session.id %}', { target: '#quiz-content' });
});
</script>
{% endblock %}