Polish dashboard: consistent icon set, flatter cards, merged activity card

Replace card-header emoji with a matching stroke-style SVG icon set that
inherits theme color via currentColor. Drop the accent-stripe border from
every card and keep it only on the Your Courses card, the one primary
action. Merge Continue Watching and Recently Viewed into a single
true-recency list so an old completed item can no longer outrank a newer
in-progress one across two separately-capped lists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 21:11:36 -04:00
co-authored by Claude Sonnet 5
parent daec0742ec
commit 3039d1206d
2 changed files with 23 additions and 38 deletions
+1 -20
View File
@@ -1790,23 +1790,6 @@ def build_study_guide_markdown(course: Course) -> str:
current_course = None current_course = None
def _split_continue_watching(all_views: List[Dict[str, Any]]) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
"""
Split the recent-views list (already carrying percent_watched/completed,
see get_recent_views_for_display) into 'Continue Watching' - genuinely
in-progress lessons - and the remaining 'Recently Viewed' entries, each
capped at 5 for the dashboard. Reuses the same underlying history rather
than a separate library-wide progress index (see plan notes).
"""
continue_watching = [v for v in all_views if 0 < v['percent_watched'] < 100][:5]
continue_ids = {(v.get('course_path'), v.get('lesson_path')) for v in continue_watching}
recent_views = [
v for v in all_views
if (v.get('course_path'), v.get('lesson_path')) not in continue_ids
][:5]
return continue_watching, recent_views
def format_duration(seconds: int) -> str: def format_duration(seconds: int) -> str:
"""Render a duration in seconds as e.g. '3h 24m' or '45m' (under an hour).""" """Render a duration in seconds as e.g. '3h 24m' or '45m' (under an hour)."""
total_minutes = max(0, int(seconds)) // 60 total_minutes = max(0, int(seconds)) // 60
@@ -1819,7 +1802,7 @@ def index():
"""Main dashboard""" """Main dashboard"""
global current_course global current_course
continue_watching, recent_views = _split_continue_watching(get_recent_views_for_display()) recent_views = get_recent_views_for_display()[:8]
if current_course is None: if current_course is None:
# One scan covers stats/stale-courses/heatmap - see _scan_library_activity. # One scan covers stats/stale-courses/heatmap - see _scan_library_activity.
@@ -1827,7 +1810,6 @@ def index():
return render_template('course_dashboard.html', return render_template('course_dashboard.html',
course=None, course=None,
stats={'total_lessons': 0, 'completed_lessons': 0, 'completion_percentage': 0}, stats={'total_lessons': 0, 'completed_lessons': 0, 'completion_percentage': 0},
continue_watching=continue_watching,
recent_views=recent_views, recent_views=recent_views,
recently_added=get_recently_added_courses(), recently_added=get_recently_added_courses(),
library_stats=format_library_stats(scan), library_stats=format_library_stats(scan),
@@ -1851,7 +1833,6 @@ def index():
return render_template('course_dashboard.html', return render_template('course_dashboard.html',
course=current_course, course=current_course,
stats=stats, stats=stats,
continue_watching=continue_watching,
recent_views=recent_views, recent_views=recent_views,
has_note=bool(course_has_any_notes(current_course)), has_note=bool(course_has_any_notes(current_course)),
is_queued=os.path.abspath(current_course.path) in get_next_up_paths(), is_queued=os.path.abspath(current_course.path) in get_next_up_paths(),
+22 -18
View File
@@ -206,6 +206,12 @@
border-radius: var(--radius); border-radius: var(--radius);
padding: 20px; padding: 20px;
margin-bottom: 20px; margin-bottom: 20px;
}
#library-card {
/* the one card that's a primary action (browse/search/load a
course) rather than a passive info/list card - every other
card getting the same accent stripe made nothing stand out */
border-left: 4px solid var(--accent); border-left: 4px solid var(--accent);
} }
@@ -380,6 +386,16 @@
transform: rotate(-90deg); transform: rotate(-90deg);
} }
.card-header-label {
display: flex;
align-items: center;
gap: 8px;
}
.card-icon {
flex-shrink: 0;
color: var(--text-primary);
}
.card-body { .card-body {
overflow: hidden; overflow: hidden;
max-height: 3000px; max-height: 3000px;
@@ -1088,7 +1104,7 @@
<div class="container"> <div class="container">
<div class="card" id="library-card"> <div class="card" id="library-card">
<h2>Your Courses</h2> <h2><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="8" height="8" rx="1.5"></rect><rect x="13" y="3" width="8" height="8" rx="1.5"></rect><rect x="3" y="13" width="8" height="8" rx="1.5"></rect><rect x="13" y="13" width="8" height="8" rx="1.5"></rect></svg>Your Courses</span></h2>
<div class="library-toolbar"> <div class="library-toolbar">
<input type="text" id="library-search-input" class="library-search-input" <input type="text" id="library-search-input" class="library-search-input"
placeholder="Search courses…" oninput="handleLibrarySearchInput(this.value)"> placeholder="Search courses…" oninput="handleLibrarySearchInput(this.value)">
@@ -1124,7 +1140,7 @@
{% if library_stats and library_stats.total_courses %} {% if library_stats and library_stats.total_courses %}
<div class="container"> <div class="container">
<div class="card"> <div class="card">
<h2 class="collapsible-header" data-card-id="library-stats">📊 Library Stats</h2> <h2 class="collapsible-header" data-card-id="library-stats"><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="20" x2="6" y2="14"></line><line x1="12" y1="20" x2="12" y2="8"></line><line x1="18" y1="20" x2="18" y2="11"></line></svg>Library Stats</span></h2>
<div class="card-body"> <div class="card-body">
<div class="stats-grid"> <div class="stats-grid">
<div class="stats-tile"> <div class="stats-tile">
@@ -1166,7 +1182,7 @@
{% if next_up %} {% if next_up %}
<div class="container"> <div class="container">
<div class="card" id="next-up-card"> <div class="card" id="next-up-card">
<h2 class="collapsible-header" data-card-id="next-up">📌 Next Up</h2> <h2 class="collapsible-header" data-card-id="next-up"><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="20" y2="6"></line><line x1="8" y1="12" x2="20" y2="12"></line><line x1="8" y1="18" x2="20" y2="18"></line><circle cx="4" cy="6" r="1"></circle><circle cx="4" cy="12" r="1"></circle><circle cx="4" cy="18" r="1"></circle></svg>Next Up</span></h2>
<div class="card-body" id="next-up-list"> <div class="card-body" id="next-up-list">
{% for item in next_up %} {% for item in next_up %}
<div class="lesson-item" data-path="{{ item.path }}"> <div class="lesson-item" data-path="{{ item.path }}">
@@ -1193,7 +1209,7 @@
{% if stale_courses %} {% if stale_courses %}
<div class="container"> <div class="container">
<div class="card"> <div class="card">
<h2 class="collapsible-header" data-card-id="pick-back-up">Pick Back Up</h2> <h2 class="collapsible-header" data-card-id="pick-back-up"><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"></path><polyline points="3 4 3 9 8 9"></polyline></svg>Pick Back Up</span></h2>
<div class="card-body"> <div class="card-body">
{% for item in stale_courses %} {% for item in stale_courses %}
{{ render_course_row(item, 'Last touched ' + item.last_touched_display) }} {{ render_course_row(item, 'Last touched ' + item.last_touched_display) }}
@@ -1205,7 +1221,7 @@
{% if recently_added %} {% if recently_added %}
<div class="container"> <div class="container">
<div class="card"> <div class="card">
<h2 class="collapsible-header" data-card-id="recently-added">🆕 Recently Added</h2> <h2 class="collapsible-header" data-card-id="recently-added"><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>Recently Added</span></h2>
<div class="card-body"> <div class="card-body">
{% for item in recently_added %} {% for item in recently_added %}
{{ render_course_row(item, item.added_display) }} {{ render_course_row(item, item.added_display) }}
@@ -1214,22 +1230,10 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if continue_watching %}
<div class="container">
<div class="card">
<h2 class="collapsible-header" data-card-id="continue-watching">▶ Continue Watching</h2>
<div class="card-body">
{% for view in continue_watching %}
{{ render_view_row(view) }}
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if recent_views %} {% if recent_views %}
<div class="container"> <div class="container">
<div class="card"> <div class="card">
<h2 class="collapsible-header" data-card-id="recently-viewed">🕐 Recently Viewed</h2> <h2 class="collapsible-header" data-card-id="recently-viewed"><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><polygon points="10 8 16 12 10 16 10 8"></polygon></svg>Recently Viewed</span></h2>
<div class="card-body"> <div class="card-body">
{% for view in recent_views %} {% for view in recent_views %}
{{ render_view_row(view) }} {{ render_view_row(view) }}