From 21af7ae34937c00f9b4cc1a06b7ece6174483cd6 Mon Sep 17 00:00:00 2001 From: rmsitz Date: Sun, 23 Aug 2026 15:30:44 -0400 Subject: [PATCH] Add colored grid-card fallbacks, bottom tab bar, dashboard reorder Three visual/structural changes: - Grid cards without a thumbnail (nearly all real courses in this library) showed the same generic graduation-cap icon, making the poster grid hard to visually scan. Replace it with a deterministic color+initial per course name (same idea as Slack/Google Photos default avatars), so cards without real cover art still look distinct from each other. - Notes/Help/Settings only existed as small footer text links, meaning a scroll to the bottom of every page to navigate. Add a persistent bottom tab bar (Home/Notes/Help/Settings, current page highlighted via a new active_tab context var) to the dashboard, Notes Hub, Settings, and Help pages. Deliberately left the lesson page alone - it already has its own fixed-bottom notes panel, and a second fixed bar there would just eat into an already content-dense page. - The no-course dashboard stacked five informational/queue cards (Library Stats, Next Up, Pick Back Up, Recently Added, Continue Watching, Recently Viewed) before "Your Courses," so the actual library browser was the last thing on the page. Move it up to right after the Surprise Me banner - it was already unconditional, so this only changes where it renders, not when. Co-Authored-By: Claude Sonnet 5 --- offlineu_core.py | 11 +- templates/course_dashboard.html | 191 +++++++++++++++++++------------- templates/help.html | 69 +++++++----- templates/notes_hub.html | 67 ++++++----- templates/settings.html | 72 ++++++------ 5 files changed, 235 insertions(+), 175 deletions(-) diff --git a/offlineu_core.py b/offlineu_core.py index 779095a..842a1ee 100644 --- a/offlineu_core.py +++ b/offlineu_core.py @@ -1833,7 +1833,8 @@ def index(): library_stats=format_library_stats(scan), stale_courses=format_stale_courses(scan), activity_heatmap=format_activity_heatmap(scan), - next_up=get_next_up_courses()) + next_up=get_next_up_courses(), + active_tab='home') # Apply progress data to tree ProgressTracker.apply_progress_to_tree(current_course) @@ -1854,7 +1855,8 @@ def index(): recent_views=recent_views, has_note=bool(course_has_any_notes(current_course)), is_queued=os.path.abspath(current_course.path) in get_next_up_paths(), - resume_lesson=resume_lesson) + resume_lesson=resume_lesson, + active_tab='home') @app.route('/browse') @@ -2252,6 +2254,7 @@ def settings_page(): density_choices=['comfortable', 'compact'], card_style_choices=['flat', 'elevated', 'bordered'], corner_radius_choices=['sharp', 'rounded', 'pill'], + active_tab='settings', ) @@ -2383,13 +2386,13 @@ def open_recent(): @app.route('/help') def help_page(): """Static help page: how to use the app, supported file types.""" - return render_template('help.html') + return render_template('help.html', active_tab='help') @app.route('/notes') def notes_hub(): """Every lesson note across the whole library in one place.""" - return render_template('notes_hub.html', notes=get_all_notes()) + return render_template('notes_hub.html', notes=get_all_notes(), active_tab='notes') @app.route('/library/search-transcripts') diff --git a/templates/course_dashboard.html b/templates/course_dashboard.html index c3ceaa8..56a2824 100644 --- a/templates/course_dashboard.html +++ b/templates/course_dashboard.html @@ -70,6 +70,7 @@ } .page-content { flex: 1; + padding-bottom: 70px; } .container { @@ -144,45 +145,35 @@ word-break: break-word; } - .app-footer { + .bottom-tab-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 9000; + display: flex; background: var(--bg-secondary); border-top: 1px solid var(--border-color); - margin-top: 40px; - padding: 18px 0; + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.2); + padding-bottom: env(safe-area-inset-bottom, 0px); } - - .app-footer .container { + .tab-item { + flex: 1; display: flex; + flex-direction: column; align-items: center; - justify-content: space-between; - gap: 15px; - flex-wrap: wrap; - } - - .footer-brand { - color: var(--text-muted); - font-size: 0.9em; - display: flex; - align-items: center; - gap: 8px; - } - - .footer-links { - display: flex; - gap: 20px; - } - - .footer-links a { + gap: 2px; + padding: 8px 4px; color: var(--text-muted); text-decoration: none; - font-size: 0.9em; - display: flex; - align-items: center; - gap: 6px; + font-size: 0.72em; transition: color 0.2s; } - - .footer-links a:hover { + .tab-item .tab-icon { + font-size: 1.3em; + } + .tab-item:hover, + .tab-item.active { color: var(--accent); } @@ -735,6 +726,17 @@ object-fit: cover; display: block; } + .grid-card-initial { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 1.6em; + font-weight: 700; + font-family: var(--font-family); + } .grid-card-name { font-size: 0.9em; font-weight: 500; @@ -1079,7 +1081,44 @@ + {% endif %} +
+
+

Your Courses

+
+ + +
+ + +
+ +
+ + +

+
+
+
+
+ + {% if library_stats and library_stats.total_courses %}

πŸ“Š Library Stats

@@ -1196,55 +1235,23 @@
{% endif %} -
-
-

Your Courses

-
- - -
- - -
- -
- - -

-
-
-
-
{% endif %} - + diff --git a/templates/notes_hub.html b/templates/notes_hub.html index cc7e17d..42eacec 100644 --- a/templates/notes_hub.html +++ b/templates/notes_hub.html @@ -57,6 +57,7 @@ .page-content { flex: 1; padding: 20px; + padding-bottom: 70px; } .app-header { background: linear-gradient(135deg, var(--bg-secondary), var(--bg-tertiary)); @@ -79,32 +80,36 @@ font-weight: 600; letter-spacing: 0.5px; } - .app-footer { + .bottom-tab-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 9000; + display: flex; background: var(--bg-secondary); border-top: 1px solid var(--border-color); - padding: 18px 0; + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.2); + padding-bottom: env(safe-area-inset-bottom, 0px); } - .app-footer .footer-inner { - max-width: 800px; - margin: 0 auto; + .tab-item { + flex: 1; display: flex; + flex-direction: column; align-items: center; - justify-content: space-between; - gap: 15px; - flex-wrap: wrap; - } - .footer-brand { + gap: 2px; + padding: 8px 4px; color: var(--text-muted); - font-size: 0.9em; - display: flex; - align-items: center; - gap: 8px; - } - .footer-links { display: flex; gap: 20px; } - .footer-links a { - color: var(--accent); text-decoration: none; - font-size: 0.9em; + font-size: 0.72em; + transition: color 0.2s; + } + .tab-item .tab-icon { + font-size: 1.3em; + } + .tab-item:hover, + .tab-item.active { + color: var(--accent); } .container { max-width: 800px; @@ -255,16 +260,20 @@ - +