diff --git a/public/calendar.js b/public/calendar.js
index 297dede..4434d0b 100644
--- a/public/calendar.js
+++ b/public/calendar.js
@@ -54,11 +54,13 @@ const calState = { y: now0.getFullYear(), m: now0.getMonth(), sel: ymd(now0) };
async function viewCalendar() {
const mode = store.get('cal_view', 'month');
const v = $('#view');
- v.innerHTML = `
${plannerHead('calendar', `
`)}
-
${[['month', 'Month'], ['week', 'Week'], ['day', 'Day'], ['agenda', 'Agenda']].map(([k, l]) => `
${l}`).join('')}
+ const views = `
${[['month', 'Month'], ['week', 'Week'], ['day', 'Day'], ['agenda', 'Agenda']].map(([k, l]) => `
${l}`).join('')}
`
+ + (mode === 'agenda' ? `
` : '');
+ v.innerHTML = `
${plannerHead('calendar', `
`, views)}
`;
$('#ne').onclick = () => newEvent(mode === 'agenda' ? ymd(new Date()) : calState.sel);
- v.querySelectorAll('.seg a[data-v]').forEach(a => a.onclick = e => { e.preventDefault(); store.set('cal_view', a.dataset.v); viewCalendar(); });
+ if ($('#crange')) $('#crange').onchange = e => { store.set('cal_range', +e.target.value); viewCalendar(); };
+ v.querySelectorAll('.sgrp a[data-v]').forEach(a => a.onclick = e => { e.preventDefault(); store.set('cal_view', a.dataset.v); viewCalendar(); });
$('#mc').onclick = manageCalendars;
try {
const cals = await jfetch('/api/cal/list');
@@ -72,10 +74,8 @@ async function renderAgendaList() {
const { byDay, errors } = await loadAgenda(range);
if (!location.hash.startsWith('#/calendar') || !$('#cbody')) return;
const keys = [...byDay.keys()].sort();
- $('#cbody').innerHTML = `
${[[1, 'Today'], [7, '7 days'], [30, '30 days']].map(([n, l]) => `
${l}`).join('')}
`
- + (keys.map(k => `
${esc(dayLabel(k))}
${byDay.get(k).map(evRow).join('')}
`).join('') || '
Nothing coming up
')
+ $('#cbody').innerHTML = (keys.map(k => `
${esc(dayLabel(k))}
${byDay.get(k).map(evRow).join('')}
`).join('') || '
Nothing coming up
')
+ errors.map(e => `
${esc(e)}
`).join('');
- $('#cbody').querySelectorAll('.seg a[data-n]').forEach(a => a.onclick = e => { e.preventDefault(); store.set('cal_range', +a.dataset.n); viewCalendar(); });
}
async function renderMonth() {
diff --git a/public/planner.js b/public/planner.js
index c740289..53a5d97 100644
--- a/public/planner.js
+++ b/public/planner.js
@@ -6,11 +6,12 @@ function viewPlanner(args = []) {
const want = args[0] && ok.includes(args[0]) ? args[0] : store.get('planner_pane', ok[0]);
location.replace('#/' + (ok.includes(want) ? want : ok[0] || 'home'));
}
-/** The page heading, with a Calendar | Tasks switch when this person may use both. `extra` (a button, say) sits on the same row, at the right. */
-function plannerHead(active, extra = '') {
+/** One compact toolbar instead of stacked rows: title, Calendar | Tasks switch, then `mid` (the view switch) and `extra` (a button) pushed to the right. */
+function plannerHead(active, extra = '', mid = '') {
try { localStorage.setItem(nk('planner_pane'), JSON.stringify(active)); } catch {}
const panes = PLANNER_PANES.filter(p => allowed(p[0]));
- const seg = panes.length < 2 ? '' : `
${panes.map(([k, l]) => `
${l}`).join('')}
`;
- const title = panes.length < 2 ? (PLANNER_PANES.find(p => p[0] === active) || [0, 'Planner'])[1] : 'Planner';
- return `
${esc(title)}
${seg || extra ? `
${seg}${extra}
` : ''}`;
+ const two = panes.length > 1;
+ const group = two ? `
${panes.map(([k, l]) => `
${l}`).join('')}
` : '';
+ const title = two ? 'Planner' : (PLANNER_PANES.find(p => p[0] === active) || [0, 'Planner'])[1];
+ return `
${esc(title)}
${group}${mid}${extra}`;
}
diff --git a/public/style.css b/public/style.css
index d591c15..9d05e9d 100644
--- a/public/style.css
+++ b/public/style.css
@@ -560,5 +560,13 @@ body.lyropen { overflow: hidden; }
body.nav-mini .ngrp { margin: 6px 14px; padding-top: 0; }
body.nav-mini .ngrp span { display: none; }
-.prow { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin: 6px 0 10px; }
-.prow .seg { margin: 0; }
+
+/* Planner toolbar: one row (wraps on phones) */
+.ptop { display: flex; align-items: center; gap: 10px 12px; flex-wrap: wrap; margin: 0 0 12px; }
+.ptop h1 { margin: 0 4px 0 0; font-size: 24px; }
+.pspace { flex: 1; }
+.sgrp { display: inline-flex; background: var(--panel2); border-radius: 999px; padding: 3px; gap: 2px; max-width: 100%; overflow-x: auto; }
+.sgrp a { padding: 6px 14px; border-radius: 999px; color: var(--muted); font-size: 14px; white-space: nowrap; min-height: 32px; display: flex; align-items: center; }
+.sgrp a.on { background: var(--accent); color: var(--accent-t); font-weight: 600; }
+.psel { width: auto; min-height: 38px; padding: 0 12px; border-radius: 999px; font-size: 14px; }
+@media (max-width: 560px) { .ptop h1 { width: 100%; } .pspace { display: none; } .ptop .btn { margin-left: auto; } }