Add school name and logo to home screen and admin header

- School name now appears on the student home screen and updates the page title
- Logo can be uploaded in Settings (PNG/JPG/GIF/WebP/SVG, max 2 MB); stored in the data volume so it persists across container restarts
- When a logo is uploaded the text school name is hidden in favour of the logo
- Admin header shows the school name instead of hardcoded "Homeschool", updates live when saved
- New server endpoints: GET /api/logo, POST /api/admin/upload-logo, DELETE /api/admin/logo
- /api/public-settings now includes school_name and has_logo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-08-10 16:46:00 -04:00
1 parent 1eedfc7715
commit 6b7baf7297
6 files changed
+135 -5

No files matched your search

+18 -2
View File
@@ -22,7 +22,7 @@
<div id="console" hidden>
<header class="admin-header">
<a href="index.html" class="admin-logo">🎓 Homeschool</a>
<a href="index.html" class="admin-logo" id="admin-school-name">🎓 Homeschool</a>
<nav class="admin-nav">
<button class="nav-pill active" data-panel="kids">🧒 Kids</button>
<button class="nav-pill" data-panel="courses">📚 Courses</button>
@@ -516,10 +516,26 @@
<h2>School</h2>
<form id="app-settings-form">
<div class="field-group">
<label for="setting-school-name">School / family name <span class="hint">(appears on printed reports)</span></label>
<label for="setting-school-name">School / family name <span class="hint">(appears on home screen and printed reports)</span></label>
<input id="setting-school-name" type="text" placeholder="e.g. Emmert Academy" style="max-width:22rem;display:block;margin-top:.35rem">
</div>
<div class="field-group" style="margin-top:1rem">
<label>School logo <span class="hint">(shown on the student home screen)</span></label>
<div id="logo-preview-wrap" style="margin:.4rem 0 .6rem">
<img id="logo-preview" src="" alt="School logo" hidden style="max-height:80px;max-width:240px;border-radius:6px;display:block;margin-bottom:.4rem">
<p id="logo-none-msg" class="hint" style="margin:0">No logo uploaded.</p>
</div>
<div class="inline-form" style="gap:.5rem;flex-wrap:wrap">
<label class="btn" style="cursor:pointer;display:inline-flex;align-items:center;gap:.3rem">
📁 Choose logo
<input id="logo-file-input" type="file" accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml" style="display:none">
</label>
<button type="button" id="logo-remove-btn" hidden>Remove logo</button>
</div>
<p class="hint" style="margin-top:.3rem">PNG, JPG, GIF, WebP, or SVG · max 2 MB</p>
</div>
<div class="field-group" style="margin-top:1rem">
<label for="setting-passing-pct">Passing grade threshold</label>
<div class="inline-form" style="margin-top:.35rem">
+47
View File
@@ -58,12 +58,17 @@ $('#pin-form').addEventListener('submit', async (e) => {
unlock();
});
function updateAdminHeader() {
$('#admin-school-name').textContent = `🎓 ${appSettings.schoolName || 'Homeschool'}`;
}
async function loadAppSettings() {
try {
const s = await api('/api/admin/app-settings');
appSettings.schoolName = s.school_name || '';
appSettings.passingPct = Number(s.passing_pct) || 80;
appSettings.weekStartDay = s.week_start_day || 'monday';
updateAdminHeader();
} catch {}
}
@@ -2551,8 +2556,49 @@ async function loadSettings() {
const radio = document.querySelector(`input[name="week_start_day"][value="${appSettings.weekStartDay}"]`);
if (radio) radio.checked = true;
$('#setting-show-emoji').checked = s.show_home_emoji !== false;
const pub = await fetch('/api/public-settings').then((r) => r.json()).catch(() => ({}));
setLogoPreview(pub.has_logo);
}
function setLogoPreview(hasLogo) {
const preview = $('#logo-preview');
const noneMsg = $('#logo-none-msg');
const removeBtn = $('#logo-remove-btn');
if (hasLogo) {
preview.src = `/api/logo?v=${Date.now()}`;
preview.hidden = false;
noneMsg.hidden = true;
removeBtn.hidden = false;
} else {
preview.hidden = true;
noneMsg.hidden = false;
removeBtn.hidden = true;
}
}
$('#logo-file-input').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
if (file.size > 2 * 1024 * 1024) { msg('Logo must be under 2 MB.'); e.target.value = ''; return; }
const b64 = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result.split(',')[1]);
reader.onerror = reject;
reader.readAsDataURL(file);
});
await api('/api/admin/upload-logo', { method: 'POST', body: { base64: b64, mime: file.type } });
e.target.value = '';
setLogoPreview(true);
msg('Logo saved.');
});
$('#logo-remove-btn').addEventListener('click', async () => {
await api('/api/admin/logo', { method: 'DELETE' });
setLogoPreview(false);
msg('Logo removed.');
});
$('#app-settings-form').addEventListener('submit', async (e) => {
e.preventDefault();
const school_name = $('#setting-school-name').value.trim();
@@ -2561,6 +2607,7 @@ $('#app-settings-form').addEventListener('submit', async (e) => {
const show_home_emoji = $('#setting-show-emoji').checked;
await api('/api/admin/app-settings', { method: 'POST', body: { school_name, passing_pct, week_start_day, show_home_emoji } });
appSettings.schoolName = school_name;
updateAdminHeader();
appSettings.passingPct = passing_pct;
if (appSettings.weekStartDay !== week_start_day) {
appSettings.weekStartDay = week_start_day;
+15
View File
@@ -247,6 +247,21 @@ async function loadHome() {
document.querySelector('.page-confetti').style.display =
pubSettings.show_home_emoji === false ? 'none' : '';
const nameTitle = document.getElementById('school-name-title');
if (pubSettings.school_name) {
nameTitle.textContent = pubSettings.school_name;
document.title = pubSettings.school_name;
}
const logoEl = document.getElementById('school-logo');
if (pubSettings.has_logo) {
logoEl.src = `/api/logo?v=${Date.now()}`;
logoEl.hidden = false;
nameTitle.hidden = true;
} else {
logoEl.hidden = true;
nameTitle.hidden = false;
}
const students = await api('/api/students');
$('#no-kids').hidden = students.length > 0;
$('#kid-cards').innerHTML = students
+2 -1
View File
@@ -34,11 +34,12 @@
<!-- Kid picker -->
<section id="view-home" class="view">
<h1 class="app-title">🎓 Homeschool</h1>
<h1 class="app-title" id="school-name-title">🎓 Homeschool</h1>
<p class="subtitle">Who's learning today?</p>
<div id="kid-cards" class="kid-cards"></div>
<p id="no-kids" class="hint" hidden>No kids yet — a parent needs to add them.</p>
<a class="parent-dash-card" href="admin.html">🔐 Parent dashboard</a>
<img id="school-logo" src="" alt="School logo" hidden>
</section>
<!-- Kid home: Today's work + nav -->
+2
View File
@@ -53,6 +53,8 @@ body {
}
.app-title { font-size: 2.4rem; margin-bottom: 0.5rem; }
#school-logo { display: block; margin: 2.5rem auto 0; max-width: min(520px, 92vw); height: auto; }
.subtitle { font-size: 1.25rem; color: var(--muted); margin-bottom: 2rem; }
.hint { color: var(--muted); margin: 1rem 0; }
.error { color: var(--bad); margin: 0.5rem 0; }