| <!DOCTYPE html> |
| <html lang="nso"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width,initial-scale=1"> |
| <title>Leotลกa la Sepedi โ Build Language Data</title> |
| <style> |
| * { box-sizing: border-box; margin: 0; padding: 0; } |
| body { font-family: -apple-system, sans-serif; background: #f5f4f0; color: #1a1a1a; } |
| .container { max-width: 650px; margin: 0 auto; padding: 15px; } |
| |
| |
| header { text-align: center; margin-bottom: 20px; } |
| h1 { font-size: 28px; margin: 10px 0; } |
| .subtitle { opacity: 0.7; font-size: 14px; } |
| |
| |
| .profile { background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 15px; border-radius: 12px; margin-bottom: 20px; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } |
| .stat { text-align: center; } |
| .stat-value { font-size: 24px; font-weight: bold; } |
| .stat-label { font-size: 12px; opacity: 0.9; } |
| .badges { grid-column: 1/-1; display: flex; gap: 8px; justify-content: center; font-size: 20px; } |
| .badge { opacity: 0.5; } |
| .badge.earned { opacity: 1; } |
| |
| |
| .task-card { background: white; padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } |
| .task-type { display: inline-block; background: #667eea; color: white; padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: bold; margin-bottom: 10px; } |
| .task-title { font-size: 18px; font-weight: bold; margin: 10px 0; } |
| .sentence { background: #f9f9f9; padding: 15px; border-left: 4px solid #667eea; border-radius: 4px; font-size: 16px; margin: 15px 0; } |
| .task-tip { background: #fff3cd; padding: 10px; border-radius: 6px; font-size: 13px; margin-bottom: 15px; } |
| .task-tip::before { content: "๐ก "; } |
| |
| |
| .options { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 15px 0; } |
| .option-btn { padding: 12px; border: 2px solid #ddd; background: white; border-radius: 8px; cursor: pointer; transition: all 0.3s; font-size: 14px; } |
| .option-btn:hover { border-color: #667eea; background: #f0f4ff; } |
| .option-btn.selected { background: #667eea; color: white; border-color: #667eea; } |
| |
| |
| .submit-btn { width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea, #764ba2); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; } |
| .submit-btn:hover { opacity: 0.9; } |
| |
| |
| .stats-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; margin: 20px 0; } |
| .stat-box { background: white; padding: 12px; border-radius: 8px; text-align: center; } |
| .stat-box-value { font-size: 20px; font-weight: bold; color: #667eea; } |
| .stat-box-label { font-size: 11px; opacity: 0.7; margin-top: 5px; } |
| |
| |
| .leaderboard { background: white; padding: 15px; border-radius: 12px; } |
| .leaderboard h3 { margin-bottom: 10px; } |
| .lb-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; font-size: 14px; } |
| .lb-rank { font-weight: bold; color: #667eea; width: 30px; } |
| .lb-pts { font-weight: bold; color: #4CAF50; } |
| |
| |
| .feedback { position: fixed; top: 20px; right: 20px; background: #4CAF50; color: white; padding: 12px 20px; border-radius: 8px; opacity: 0; animation: slideIn 0.3s forwards; } |
| @keyframes slideIn { to { opacity: 1; } } |
| |
| |
| @media (max-width: 600px) { |
| .options { grid-template-columns: 1fr; } |
| .stats-grid { grid-template-columns: 1fr; } |
| .profile { grid-template-columns: 1fr; } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <header> |
| <h1>๐ Leotลกa la Sepedi</h1> |
| <p class="subtitle">Help build Sepedi language data</p> |
| </header> |
| |
| |
| <div class="profile"> |
| <div class="stat"> |
| <span class="stat-value">{{ stats.points }}</span> |
| <span class="stat-label">Points</span> |
| </div> |
| <div class="stat"> |
| <span class="stat-value">Lvl {{ stats.level }}</span> |
| <span class="stat-label">Level</span> |
| </div> |
| <div class="badges"> |
| <span class="badge {% if 'bronze' in stats.badges %}earned{% endif %}">๐ฅ</span> |
| <span class="badge {% if 'silver' in stats.badges %}earned{% endif %}">๐ฅ</span> |
| <span class="badge {% if 'gold' in stats.badges %}earned{% endif %}">๐ฅ</span> |
| </div> |
| </div> |
| |
| |
| <div> |
| <h3 style="margin-bottom: 10px;">๐ Data Collected</h3> |
| <div class="stats-grid"> |
| {% for type_key, type_data in collection_stats.by_type.items %} |
| <div class="stat-box"> |
| <span style="font-size: 24px;">{{ type_data.icon }}</span> |
| <div class="stat-box-value">{{ type_data.count }}</div> |
| <div class="stat-box-label">{{ type_data.name|truncatewords:2 }}</div> |
| </div> |
| {% endfor %} |
| </div> |
| </div> |
| |
| |
| {% if task.sentence %} |
| <div class="task-card"> |
| <span class="task-type">{{ task.config.name }}</span> |
| <div class="task-title">{{ task.config.description }}</div> |
| |
| <div class="sentence">"{{ task.sentence.text }}"</div> |
| |
| <div class="task-tip">{{ task.config.tip }}</div> |
| |
| <form id="taskForm" method="POST"> |
| {% csrf_token %} |
| <input type="hidden" name="task_type" value="{{ task.task_type }}"> |
| <input type="hidden" name="sentence_id" value="{{ task.sentence.id }}"> |
| |
| {% if task.config.type != 'text_input' %} |
| <div class="options"> |
| {% for opt in task.config.options %} |
| <button type="button" class="option-btn" data-value="{{ opt.value }}"> |
| {{ opt.emoji|default:'' }} {{ opt.label }} |
| </button> |
| {% endfor %} |
| </div> |
| <input type="hidden" name="value" id="valueInput" required> |
| {% else %} |
| <input type="text" name="value" placeholder="Your answer..." style="width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px;" required> |
| {% endif %} |
| |
| <button type="submit" class="submit-btn"> |
| โ Next (+{{ task.config.points }} pts) |
| </button> |
| </form> |
| </div> |
| {% else %} |
| <div class="task-card"> |
| <h2>๐ All Sentences Labeled!</h2> |
| <p>Thank you! Check back soon for more.</p> |
| </div> |
| {% endif %} |
| |
| |
| <div class="leaderboard"> |
| <h3>๐ Top Contributors</h3> |
| {% for lb in leaderboard %} |
| <div class="lb-item"> |
| <span class="lb-rank">#{{ forloop.counter }}</span> |
| <span>{% if lb.voter_id == voter.voter_id %}๐ค YOU{% else %}Contributor{% endif %}</span> |
| <span class="lb-pts">{{ lb.points }} pts</span> |
| </div> |
| {% endfor %} |
| </div> |
| </div> |
| |
| <script> |
| |
| document.querySelectorAll('.option-btn').forEach(btn => { |
| btn.addEventListener('click', function(e) { |
| e.preventDefault(); |
| document.querySelectorAll('.option-btn').forEach(b => b.classList.remove('selected')); |
| this.classList.add('selected'); |
| document.getElementById('valueInput').value = this.dataset.value; |
| }); |
| }); |
| |
| |
| document.getElementById('taskForm').addEventListener('submit', async function(e) { |
| e.preventDefault(); |
| const formData = new FormData(this); |
| const response = await fetch('', { method: 'POST', body: formData }); |
| const data = await response.json(); |
| |
| if (data.success) { |
| |
| const msg = document.createElement('div'); |
| msg.className = 'feedback'; |
| msg.textContent = data.message; |
| document.body.appendChild(msg); |
| |
| |
| setTimeout(() => location.reload(), 500); |
| } |
| }); |
| </script> |
| </body> |
| </html> |
|
|