Replace all OS-rendered emoji with a consistent SVG icon set

Add a shared Jinja icon macro (templates/_icons.html) with 27 Feather-style
stroke SVGs and import it across every live template - tab bars, buttons,
lesson-type/status icons, folder/course icons, and the lesson player's
footer brand mark all now use currentColor SVGs instead of emoji, matching
the app's vector logo instead of rendering inconsistently per platform.
A few icons built dynamically in JS (thumbnail-load fallbacks, toggled
button states) get a small ICON_SVGS constant per file, since JS template
literals can't call the Jinja macro.

Left pure directional glyphs (expand/collapse chevrons, move up/down
arrows, keyboard-shortcut arrows) as plain Unicode - they already render
as monochrome text, matching the existing collapsible-header chevron
pattern rather than the colorful pictographic emoji this was aimed at.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 21:49:10 -04:00
co-authored by Claude Sonnet 5
parent e2db5ecef8
commit eae6b09ecd
6 changed files with 154 additions and 90 deletions
+29 -13
View File
@@ -1,3 +1,4 @@
{% import '_icons.html' as icons %}
<!DOCTYPE html>
<html lang="en">
<head>
@@ -128,6 +129,11 @@
.footer-links a:hover {
color: var(--accent);
}
.icon {
display: inline-block;
vertical-align: -3px;
flex-shrink: 0;
}
.container {
max-width: var(--container-max-width);
width: 95%;
@@ -558,7 +564,7 @@
<div style="text-align: center; color: var(--text-muted);">Loading content...</div>
</div>
<a href="/files/{{ text_file|replace('\\', '/') }}" class="file-link" target="_blank">
📄 Open {{ text_file.split('/')[-1] }} in new tab
{{ icons.icon('file-text', 14) }} Open {{ text_file.split('/')[-1] }} in new tab
</a>
</div>
{% endfor %}
@@ -580,15 +586,15 @@
<a class="section-lesson-row" href="/lesson/{{ item.url }}">
{% endif %}
<span class="section-lesson-icon">
{% if item.lesson_type == 'video' %}🎥
{% elif item.lesson_type == 'audio' %}🎵
{% elif item.lesson_type == 'quiz' %}📝
{% elif item.lesson_type == 'mixed' %}📦
{% else %}📄{% endif %}
{% if item.lesson_type == 'video' %}{{ icons.icon('video', 16) }}
{% elif item.lesson_type == 'audio' %}{{ icons.icon('music', 16) }}
{% elif item.lesson_type == 'quiz' %}{{ icons.icon('clipboard', 16) }}
{% elif item.lesson_type == 'mixed' %}{{ icons.icon('package', 16) }}
{% else %}{{ icons.icon('file-text', 16) }}{% endif %}
</span>
<span class="section-lesson-title">{{ item.title }}</span>
{% if item.completed %}
<span class="section-lesson-status completed"></span>
<span class="section-lesson-status completed">{{ icons.icon('check', 14) }}</span>
{% elif item.percent_watched %}
<span class="section-lesson-status">{{ item.percent_watched }}%</span>
{% endif %}
@@ -650,6 +656,15 @@
</div>
<script>
// Client-rendered mirror of a few templates/_icons.html entries -
// JS template literals can't call the Jinja macro, so the icons
// built dynamically here are kept in sync with the same path
// data by hand.
const ICON_SVGS = {
pencil: '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"></path></svg>',
trash: '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16"></path><path d="M10 11v6M14 11v6"></path><path d="M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12"></path><path d="M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3"></path></svg>',
};
// Load text content
{% for text_file in lesson.text_files %}
console.log('Loading file: {{ text_file }}');
@@ -852,8 +867,8 @@
return `<div class="note-entry" data-note-id="${n.id}">
${badge}
<span class="note-entry-text">${escapeHtml(n.text)}</span>
<button type="button" class="note-entry-edit" onclick="startEditNote('${n.id}')" title="Edit">✏️</button>
<button type="button" class="note-entry-delete" onclick="deleteNoteEntry('${n.id}')" title="Delete">🗑</button>
<button type="button" class="note-entry-edit" onclick="startEditNote('${n.id}')" title="Edit">${ICON_SVGS.pencil}</button>
<button type="button" class="note-entry-delete" onclick="deleteNoteEntry('${n.id}')" title="Delete">${ICON_SVGS.trash}</button>
</div>`;
}).join('');
}
@@ -1313,12 +1328,13 @@
<footer class="app-footer">
<div class="container-inner">
<div class="footer-brand">
📚 <a href="/reset_course" style="color: inherit; text-decoration: none;">OfflineU</a>
<svg class="icon" viewBox="0 0 64 64" width="16" height="16" aria-hidden="true"><rect x="2" y="2" width="60" height="60" rx="16" fill="var(--accent)"></rect><path d="M24 20 L24 44 L46 32 Z" fill="var(--bg-secondary)"></path></svg>
<a href="/reset_course" style="color: inherit; text-decoration: none;">OfflineU</a>
</div>
<div class="footer-links">
<a href="/notes">📝 Notes</a>
<a href="/help"> Help</a>
<a href="/settings"> Settings</a>
<a href="/notes">{{ icons.icon('pencil', 14) }} Notes</a>
<a href="/help">{{ icons.icon('help', 14) }} Help</a>
<a href="/settings">{{ icons.icon('settings', 14) }} Settings</a>
</div>
</div>
</footer>