Files
offlineu/templates/notes_hub.html
T
rmsitzandClaude Sonnet 5 21af7ae349 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 <noreply@anthropic.com>
2026-08-23 15:30:44 -04:00

297 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Notes - OfflineU</title>
<link rel="manifest" href="/static/manifest.json">
<meta name="theme-color" content="#007acc">
<link rel="apple-touch-icon" href="/static/icons/icon-192.png">
<style>
:root {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3d3d3d;
--bg-tertiary-hover: #404040;
--text-primary: #e0e0e0;
--text-muted: #999;
--border-color: #555;
--accent: #007acc;
--accent-hover: #005a9e;
--font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
--font-size-base: 16px;
--container-max-width: 1600px;
--radius: 8px;
--success: #28a745;
--error: #ff6b6b;
}
[data-theme="light"] {
--bg-primary: #f2f2f2;
--bg-secondary: #ffffff;
--bg-tertiary: #eeeeee;
--bg-tertiary-hover: #e2e2e2;
--text-primary: #222222;
--text-muted: #666666;
--border-color: #cccccc;
}
[data-card-style="elevated"] .card,
[data-card-style="elevated"] .note-card {
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}
[data-card-style="bordered"] .card,
[data-card-style="bordered"] .note-card {
border: 1px solid var(--border-color);
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
font-family: var(--font-family);
font-size: var(--font-size-base);
background: var(--bg-primary);
color: var(--text-primary);
margin: 0;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.page-content {
flex: 1;
padding: 20px;
padding-bottom: 70px;
}
.app-header {
background: linear-gradient(135deg, var(--bg-secondary), var(--bg-tertiary));
padding: 18px 0;
border-bottom: 3px solid var(--accent);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
}
.app-header .header-inner {
max-width: 800px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 12px;
}
.app-header .brand-icon { font-size: 1.6em; }
.app-header a.brand-link {
color: var(--accent);
text-decoration: none;
font-size: 1.3em;
font-weight: 600;
letter-spacing: 0.5px;
}
.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);
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.2);
padding-bottom: env(safe-area-inset-bottom, 0px);
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
padding: 8px 4px;
color: var(--text-muted);
text-decoration: none;
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;
margin: 0 auto;
}
h1 { color: var(--accent); margin-bottom: 5px; }
.subtitle { color: var(--text-muted); margin-bottom: 25px; }
.empty-hint {
color: var(--text-muted);
text-align: center;
padding: 40px 20px;
}
.notes-search-input {
width: 100%;
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 10px 14px;
font-size: 0.95em;
font-family: var(--font-family);
margin-bottom: 20px;
}
.note-card {
background: var(--bg-secondary);
border-radius: var(--radius);
padding: 18px 20px;
margin-bottom: 15px;
border-left: 4px solid var(--accent);
}
.note-card-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.note-card-icon {
width: 32px;
height: 32px;
border-radius: 4px;
object-fit: cover;
flex-shrink: 0;
font-size: 1.3em;
display: flex;
align-items: center;
justify-content: center;
}
.note-card-titles {
flex: 1;
min-width: 0;
}
.note-lesson-title {
display: block;
font-weight: 600;
}
.note-course-name {
display: block;
font-size: 0.85em;
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.outline-badge {
font-size: 0.75em;
background: var(--bg-primary);
color: var(--accent);
padding: 2px 8px;
border-radius: 3px;
border: 1px solid var(--accent);
white-space: nowrap;
flex-shrink: 0;
}
.timestamp-badge {
font-size: 0.75em;
background: var(--bg-primary);
color: var(--text-muted);
padding: 2px 8px;
border-radius: 3px;
border: 1px solid var(--border-color);
white-space: nowrap;
flex-shrink: 0;
}
.note-card-body {
white-space: pre-wrap;
word-break: break-word;
line-height: 1.5;
}
.note-card-link {
display: inline-block;
margin-top: 12px;
font-size: 0.85em;
color: var(--accent);
text-decoration: none;
padding: 4px 10px;
border-radius: var(--radius);
transition: background 0.2s ease;
}
.note-card-link:hover {
background: var(--bg-tertiary);
text-decoration: underline;
}
</style>
</head>
<body>
<div class="app-header">
<div class="header-inner">
<span class="brand-icon">📚</span>
<a href="/reset_course" class="brand-link">OfflineU</a>
</div>
</div>
<div class="page-content">
<div class="container">
<h1>📝 Notes</h1>
<p class="subtitle">Every lesson note across your library, newest first.</p>
{% if not notes %}
<div class="empty-hint">No notes yet - jot something down on a lesson page and it'll show up here.</div>
{% else %}
<input type="text" id="notes-search-input" class="notes-search-input"
placeholder="Search notes, lessons, or courses…" oninput="filterNotes(this.value)">
<div id="notes-no-results-hint" class="empty-hint" style="display: none;">No notes match your search.</div>
{% endif %}
{% for note in notes %}
<div class="note-card" data-search="{{ (note.lesson_title ~ ' ' ~ note.course_name ~ ' ' ~ note.text) | lower }}">
<div class="note-card-header">
<span class="note-card-icon">📝</span>
<div class="note-card-titles">
<span class="note-lesson-title">{{ note.lesson_title }}</span>
<span class="note-course-name">{{ note.course_name }}</span>
</div>
{% if note.timestamp_label %}
<span class="timestamp-badge">{{ note.timestamp_label }}</span>
{% endif %}
{% if note.pushed_to_outline %}
<span class="outline-badge">Outline</span>
{% endif %}
</div>
<div class="note-card-body">{{ note.text }}</div>
<a class="note-card-link"
href="/recent/open?course_path={{ note.course_path | urlencode }}&lesson_path={{ note.lesson_path | urlencode }}{% if note.timestamp_seconds is not none %}&t={{ note.timestamp_seconds }}{% endif %}">
Open lesson →
</a>
</div>
{% endfor %}
</div>
</div>
<nav class="bottom-tab-bar">
<a href="/reset_course" class="tab-item {% if active_tab == 'home' %}active{% endif %}">
<span class="tab-icon">🏠</span><span class="tab-label">Home</span>
</a>
<a href="/notes" class="tab-item {% if active_tab == 'notes' %}active{% endif %}">
<span class="tab-icon">📝</span><span class="tab-label">Notes</span>
</a>
<a href="/help" class="tab-item {% if active_tab == 'help' %}active{% endif %}">
<span class="tab-icon"></span><span class="tab-label">Help</span>
</a>
<a href="/settings" class="tab-item {% if active_tab == 'settings' %}active{% endif %}">
<span class="tab-icon"></span><span class="tab-label">Settings</span>
</a>
</nav>
<script>
function filterNotes(query) {
const q = query.trim().toLowerCase();
const cards = document.querySelectorAll('.note-card');
let visibleCount = 0;
cards.forEach(function(card) {
const match = !q || card.dataset.search.includes(q);
card.style.display = match ? '' : 'none';
if (match) visibleCount++;
});
const noResultsHint = document.getElementById('notes-no-results-hint');
if (noResultsHint) {
noResultsHint.style.display = (visibleCount === 0 && cards.length > 0) ? 'block' : 'none';
}
}
</script>
<script src="/static/theme.js"></script>
</body>
</html>