dashboard.html (8342B)
1 {% extends "base.html" %} 2 3 {% block title %}Dashboard - StenoDojo{% endblock %} 4 5 {% block content %} 6 <div class="dashboard"> 7 {% if not has_dicts %} 8 <div class="dict-banner"> 9 <div class="dict-banner__icon"> 10 <svg viewBox="0 0 24 24" fill="none" width="28" height="28"> 11 <rect x="1" y="3" width="9" height="11" rx="2.5" fill="currentColor" opacity="0.6"/> 12 <rect x="14" y="3" width="9" height="11" rx="2.5" fill="currentColor"/> 13 <rect x="5" y="16" width="14" height="6" rx="2.5" fill="currentColor" opacity="0.8"/> 14 </svg> 15 </div> 16 <div class="dict-banner__text"> 17 <strong>No dictionaries loaded.</strong> Upload your Plover config in <a href="/settings">Settings</a> to get stroke hints and unlock all practice modes. 18 </div> 19 </div> 20 {% endif %} 21 <section class="hero-stats"> 22 <div class="stat-card stat-card--streak"> 23 <div class="stat-card__icon streak-fire"></div> 24 <div class="stat-card__value">{{ streak }}</div> 25 <div class="stat-card__label">Day Streak</div> 26 </div> 27 <div class="stat-card"> 28 <div class="stat-card__icon stat-icon--words"></div> 29 <div class="stat-card__value">{{ today.words }}</div> 30 <div class="stat-card__label">Words Today 31 {% if today.words > 0 %} 32 <span class="stat-card__sub">{{ today.accuracy | round(0) | int }}% accuracy</span> 33 {% endif %} 34 </div> 35 </div> 36 <div class="stat-card"> 37 <div class="stat-card__icon stat-icon--mastered"></div> 38 <div class="stat-card__value">{{ words_mastered }}</div> 39 <div class="stat-card__label">Words Mastered</div> 40 </div> 41 <div class="stat-card"> 42 <div class="stat-card__icon stat-icon--review"></div> 43 <div class="stat-card__value">{{ review_due }}</div> 44 <div class="stat-card__label">Review Due 45 {% if review_due > 0 %} 46 <a href="/practice?mode=review" class="stat-card__action">Start Review</a> 47 {% endif %} 48 </div> 49 </div> 50 </section> 51 52 <section> 53 <h2 class="section-title">Guided Lessons</h2> 54 <div class="lesson-grid"> 55 {% for lesson in lessons %} 56 <a href="/practice?mode=lesson&lesson={{ lesson.id }}" class="lesson-card lesson-card--{{ lesson.level }}"> 57 <div class="lesson-card__order">{{ lesson.order }}</div> 58 <div class="lesson-card__info"> 59 <div class="lesson-card__title">{{ lesson.title }}</div> 60 <div class="lesson-card__subtitle">{{ lesson.subtitle }}</div> 61 {% if lesson.progress %} 62 {% set prog = lesson.progress %} 63 {% set pct = (prog.mastered / prog.total_words * 100) | round(0) | int if prog.total_words > 0 else 0 %} 64 <div class="lesson-progress"> 65 <div class="lesson-progress__bar"> 66 <div class="lesson-progress__fill lesson-progress__fill--practiced" style="width: {{ (prog.practiced / prog.total_words * 100) | round(0) | int if prog.total_words > 0 else 0 }}%"></div> 67 <div class="lesson-progress__fill lesson-progress__fill--mastered" style="width: {{ pct }}%"></div> 68 </div> 69 <div class="lesson-progress__text"> 70 <span>{{ prog.mastered }}/{{ prog.total_words }} mastered</span> 71 {% if prog.avg_wpm > 0 %} 72 <span>{{ prog.avg_wpm }} WPM</span> 73 {% endif %} 74 </div> 75 </div> 76 {% endif %} 77 </div> 78 <div class="lesson-card__level badge badge--{{ lesson.level }}">{{ lesson.level }}</div> 79 </a> 80 {% endfor %} 81 </div> 82 </section> 83 84 <section class="quick-start"> 85 <h2 class="section-title">Free Practice</h2> 86 <div class="mode-grid"> 87 <a href="/practice?mode=common" class="mode-card"> 88 <div class="mode-card__icon mode-icon--common"></div> 89 <div class="mode-card__title">Common Words</div> 90 <div class="mode-card__desc">Most frequent words</div> 91 </a> 92 <a href="/practice?mode=speed" class="mode-card"> 93 <div class="mode-card__icon mode-icon--speed"></div> 94 <div class="mode-card__title">Speed Drill</div> 95 <div class="mode-card__desc">Single-stroke words</div> 96 </a> 97 <a href="/practice?mode=phrases" class="mode-card"> 98 <div class="mode-card__icon mode-icon--phrases"></div> 99 <div class="mode-card__title">Phrases</div> 100 <div class="mode-card__desc">Multi-word phrases</div> 101 </a> 102 <a href="/practice?mode=sentences" class="mode-card"> 103 <div class="mode-card__icon mode-icon--sentences"></div> 104 <div class="mode-card__title">Sentences</div> 105 <div class="mode-card__desc">Full sentences</div> 106 </a> 107 <a href="/practice?mode=new" class="mode-card"> 108 <div class="mode-card__icon mode-icon--new"></div> 109 <div class="mode-card__title">New Words</div> 110 <div class="mode-card__desc">Words you haven't seen</div> 111 </a> 112 <a href="/practice?mode=weak" class="mode-card"> 113 <div class="mode-card__icon mode-icon--weak"></div> 114 <div class="mode-card__title">Weak Words</div> 115 <div class="mode-card__desc">Your trouble spots</div> 116 </a> 117 <a href="/practice?mode=review" class="mode-card"> 118 <div class="mode-card__icon mode-icon--review"></div> 119 <div class="mode-card__title">Review</div> 120 <div class="mode-card__desc">Spaced repetition</div> 121 </a> 122 <a href="/practice?mode=random" class="mode-card"> 123 <div class="mode-card__icon mode-icon--random"></div> 124 <div class="mode-card__title">Random</div> 125 <div class="mode-card__desc">Mix of all words</div> 126 </a> 127 </div> 128 </section> 129 130 <section class="today-progress"> 131 <h2 class="section-title">Today's Progress</h2> 132 <div class="progress-container"> 133 <div class="progress-bar"> 134 <div class="progress-bar__fill" style="width: {{ [today.words / 100 * 100, 100] | min }}%"></div> 135 </div> 136 <div class="progress-bar__label">{{ today.words }} / 100 words</div> 137 </div> 138 </section> 139 140 <section class="recent-sessions"> 141 <h2 class="section-title">Recent Sessions</h2> 142 {% if recent_sessions %} 143 <div class="table-wrapper"> 144 <table class="table"> 145 <thead> 146 <tr> 147 <th>Mode</th> 148 <th>Date</th> 149 <th>Words</th> 150 <th>Accuracy</th> 151 <th>WPM</th> 152 </tr> 153 </thead> 154 <tbody> 155 {% for session in recent_sessions[:5] %} 156 <tr> 157 <td><span class="badge badge--{{ session.mode }}">{{ session.mode }}</span></td> 158 <td>{{ session.date_str }}</td> 159 <td>{{ session.words_correct }} / {{ session.words_attempted }}</td> 160 <td class="accuracy-cell accuracy--{{ 'good' if session.accuracy >= 90 else ('mid' if session.accuracy >= 70 else 'low') }}">{{ session.accuracy | round(0) | int }}%</td> 161 <td>{{ session.wpm | round(1) }}</td> 162 </tr> 163 {% endfor %} 164 </tbody> 165 </table> 166 </div> 167 {% else %} 168 <div class="empty-state"> 169 <p>No sessions yet. Start practicing to see your progress here.</p> 170 </div> 171 {% endif %} 172 </section> 173 </div> 174 {% endblock %}