Add morning briefing, command palette, and boot animations

#11 Smart Morning Briefing: auto-speaks once per day before noon — fetches
tasks, appointments, active alerts, and weather, composes a ~2-sentence TTS
summary. Stored in localStorage (jarvis_brief_YYYY-MM-DD) to fire only once.

#12 Quick Command Palette (Ctrl+K): frosted-glass overlay with 20 pre-loaded
commands across 6 groups (Network/Agents/Planner/Media/Smart Home/System).
Fuzzy filter as you type, arrow-key navigation, Enter to fire. Matches are
highlighted. Backdrop click or Escape to close.

#13 Live Boot Animation: stat bars and numbers now count from 0 on first render
via tickTo() change. Arc Reactor rings spin in with staggered delays (0.08s
per ring) on login using boot-spin CSS class + @keyframes arcBootSpin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 03:00:16 +00:00
parent 462ce257a8
commit b2aa3280e1
4 changed files with 252 additions and 1 deletions
+48 -1
View File
@@ -135,6 +135,21 @@ function showApp(name, greeting, silent = false) {
}
}
// Smart morning briefing: auto-speak once per day before noon
const _briefKey = 'jarvis_brief_' + new Date().toISOString().slice(0, 10);
const _briefHour = new Date().getHours();
if (!silent && _briefHour < 12 && !localStorage.getItem(_briefKey)) {
localStorage.setItem(_briefKey, '1');
setTimeout(triggerMorningBriefing, 3500);
}
// Arc Reactor boot spin-up
const _ar = document.getElementById('arcReactor');
if (_ar) {
_ar.classList.add('boot-spin');
setTimeout(() => _ar.classList.remove('boot-spin'), 1600);
}
// Start data refresh
initCollapsiblePanels();
refreshAll();
@@ -419,7 +434,7 @@ const _prevVals = {};
function tickTo(id, newVal, unit='%', decimals=0) {
const el = document.getElementById(id);
if (!el) return;
const prev = _prevVals[id] ?? newVal;
const prev = _prevVals[id] !== undefined ? _prevVals[id] : 0;
_prevVals[id] = newVal;
if (Math.abs(newVal - prev) < 0.5) { el.textContent = newVal.toFixed(decimals) + unit; return; }
const start = performance.now(), dur = 700;
@@ -1480,3 +1495,35 @@ async function checkAgentStatus() {
if (sta) sta.textContent = 'ERROR';
}
}
// ── SMART MORNING BRIEFING ─────────────────────────────────────────────────
async function triggerMorningBriefing() {
try {
const [planner, alerts, weather] = await Promise.all([
api('planner/today').catch(() => null),
api('alerts').catch(() => null),
api('weather').catch(() => null),
]);
const tasks = (planner?.tasks || []).filter(t => t.status !== 'done');
const appts = planner?.appointments || [];
const active = (alerts?.alerts || alerts || []).filter(a =>
a.severity === 'critical' || a.severity === 'warning');
const temp = weather?.current?.temp_f ?? weather?.current?.temp ?? null;
const cond = weather?.current?.condition?.text ?? weather?.current?.description ?? null;
const parts = [];
if (tasks.length > 0) parts.push(`${tasks.length} task${tasks.length > 1 ? 's' : ''} due today`);
if (appts.length > 0) parts.push(`${appts.length} appointment${appts.length > 1 ? 's' : ''} on the calendar`);
if (active.length > 0) parts.push(`${active.length} active alert${active.length > 1 ? 's' : ''} requiring attention`);
if (temp !== null) parts.push(`currently ${Math.round(temp)}°${cond ? ' and ' + cond.toLowerCase() : ''}`);
const name = sessionUser || 'sir';
const msg = parts.length > 0
? `Good morning, ${name}. ${parts.join(', ')}. Systems nominal — ready when you are.`
: `Good morning, ${name}. No tasks or alerts today — clear skies ahead. All systems nominal.`;
addMessage('jarvis', msg);
speak(msg);
} catch(e) {}
}