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:
+30
-12
@@ -1,3 +1,4 @@
|
||||
{% import '_icons.html' as icons %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -112,6 +113,11 @@
|
||||
.tab-item.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
.icon {
|
||||
display: inline-block;
|
||||
vertical-align: -3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
@@ -439,7 +445,7 @@
|
||||
</div>
|
||||
<span id="library-path-status" style="font-size: 0.85em; min-height: 1.2em;"></span>
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<button class="btn btn-secondary btn-sm" onclick="refreshLibraryCache()">🔄 Refresh Library</button>
|
||||
<button class="btn btn-secondary btn-sm" onclick="refreshLibraryCache()">{{ icons.icon('refresh', 14) }} Refresh Library</button>
|
||||
<span id="library-refresh-status" style="font-size: 0.85em; color: var(--text-muted);"></span>
|
||||
</div>
|
||||
<span class="setting-desc">Courses are cached briefly for speed - use this after adding or removing files directly on disk if you don't want to wait a few minutes for it to notice.</span>
|
||||
@@ -566,28 +572,40 @@
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-secondary" onclick="resetSettings()">Reset to Defaults</button>
|
||||
<span id="save-status">✓ Saved</span>
|
||||
<span id="save-status">{{ icons.icon('check', 14) }} Saved</span>
|
||||
</div>
|
||||
</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>
|
||||
<span class="tab-icon">{{ icons.icon('home', 20) }}</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>
|
||||
<span class="tab-icon">{{ icons.icon('pencil', 20) }}</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>
|
||||
<span class="tab-icon">{{ icons.icon('help', 20) }}</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>
|
||||
<span class="tab-icon">{{ icons.icon('settings', 20) }}</span><span class="tab-label">Settings</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<script src="/static/theme.js"></script>
|
||||
<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 = {
|
||||
ban: '<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"><circle cx="12" cy="12" r="9"></circle><line x1="5.6" y1="5.6" x2="18.4" y2="18.4"></line></svg>',
|
||||
'graduation-cap': '<svg class="icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 9 12 4 2 9l10 5 10-5Z"></path><path d="M6 11v5c0 1.4 2.7 2.5 6 2.5s6-1.1 6-2.5v-5"></path></svg>',
|
||||
folder: '<svg class="icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"></path></svg>',
|
||||
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>',
|
||||
check: '<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"><polyline points="20 6 9 17 4 12"></polyline></svg>',
|
||||
};
|
||||
|
||||
let saveTimeout = null;
|
||||
|
||||
function showSaved() {
|
||||
@@ -652,7 +670,7 @@
|
||||
list.innerHTML = items.map(item => `
|
||||
<div class="curate-row">
|
||||
<div class="curate-row-name">
|
||||
<span>🚫</span>
|
||||
<span>${ICON_SVGS.ban}</span>
|
||||
<span class="name">${item.name}</span>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm" onclick="toggleHidden('${item.path.replace(/'/g, "\\'")}', false)">Show</button>
|
||||
@@ -686,9 +704,9 @@
|
||||
container.innerHTML = data.items.map(item => {
|
||||
const safePath = item.path.replace(/'/g, "\\'");
|
||||
const safeName = item.name.replace(/'/g, "\\'");
|
||||
const icon = item.type === 'course' ? '🎓' : '📁';
|
||||
const icon = item.type === 'course' ? ICON_SVGS['graduation-cap'] : ICON_SVGS.folder;
|
||||
const hiddenClass = item.hidden ? 'is-hidden' : '';
|
||||
const renameBtn = `<button class="btn btn-secondary btn-sm" onclick="event.stopPropagation(); startRename(this, '${safePath}', '${safeName}')" title="Rename">✏️</button>`;
|
||||
const renameBtn = `<button class="btn btn-secondary btn-sm" onclick="event.stopPropagation(); startRename(this, '${safePath}', '${safeName}')" title="Rename">${ICON_SVGS.pencil}</button>`;
|
||||
const toggleBtn = `<button class="btn btn-secondary btn-sm" onclick="event.stopPropagation(); toggleHidden('${safePath}', ${!item.hidden}, this)">${item.hidden ? 'Show' : 'Hide'}</button>`;
|
||||
const actions = `<div class="curate-actions">${renameBtn}${toggleBtn}</div>`;
|
||||
|
||||
@@ -951,7 +969,7 @@
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
status.style.color = 'var(--success)';
|
||||
status.textContent = '✓ Saved — reload the main page to see it take effect';
|
||||
status.innerHTML = ICON_SVGS.check + ' Saved — reload the main page to see it take effect';
|
||||
showSaved();
|
||||
} else {
|
||||
status.style.color = 'var(--error)';
|
||||
@@ -972,7 +990,7 @@
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
status.style.color = data.success ? 'var(--success)' : 'var(--error)';
|
||||
status.textContent = data.success ? '✓ Refreshed' : 'Could not refresh';
|
||||
status.innerHTML = data.success ? (ICON_SVGS.check + ' Refreshed') : 'Could not refresh';
|
||||
if (data.success) setTimeout(() => { status.textContent = ''; }, 3000);
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -1004,7 +1022,7 @@
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
status.style.color = 'var(--success)';
|
||||
status.textContent = `✓ Loaded "${data.course_name}" — redirecting...`;
|
||||
status.innerHTML = ICON_SVGS.check + ` Loaded "${data.course_name}" — redirecting...`;
|
||||
setTimeout(() => { window.location.href = '/'; }, 1000);
|
||||
} else {
|
||||
status.style.color = 'var(--error)';
|
||||
|
||||
Reference in New Issue
Block a user