Make the weather strip and stat tiles shorter
All checks were successful
Build and push image / build (push) Successful in 13s

The wallboard is already scaled, so both were taking height from the
technician table. The weather strip puts the temperature and conditions
on one line, the details in two columns, and each forecast day's icon
beside its temperatures. Stat tiles show the number with its note on
one line, with tighter padding, and "Unassigned requests" becomes
"Unassigned" so every label fits on one line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Skylar Grant 2026-10-19 12:30:23 -04:00
parent 7141a689b4
commit b1171aa398

View File

@ -238,36 +238,44 @@ button.quiet:disabled { opacity: 0.5; cursor: default; }
/* tiles share rows, so a label that wraps beside the table keeps every number on one line */ /* tiles share rows, so a label that wraps beside the table keeps every number on one line */
.stats .stat { .stats .stat {
display: grid; display: grid;
grid-row: span 3; grid-row: span 2;
grid-template-rows: subgrid; grid-template-rows: subgrid;
row-gap: 0; row-gap: 0;
align-content: start; align-content: start;
} }
.stats .stat .label { align-self: end; } .stats .stat .label { align-self: end; }
/* kept short so the table below gets the height: label, then the number with its note beside it */
.stat { .stat {
padding: 16px 18px; padding: 10px 14px;
background: var(--card); background: var(--card);
border: 1px solid var(--rule); border: 1px solid var(--rule);
} }
.stat .label { .stat .label {
font-family: var(--mono); font-family: var(--mono);
font-size: 12px; font-size: 12px;
letter-spacing: 0.08em; letter-spacing: 0.06em;
text-transform: uppercase; text-transform: uppercase;
color: var(--muted); color: var(--muted);
} }
.stat .reading {
display: flex;
align-items: baseline;
gap: 8px;
min-width: 0;
margin-top: 2px;
}
.stat .value { .stat .value {
margin-top: 4px; flex: none;
font-family: var(--mono); font-family: var(--mono);
font-size: 30px; font-size: 24px;
font-weight: 600; font-weight: 600;
line-height: 1.1; line-height: 1.15;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.stat.accent .value { color: var(--signal); } .stat.accent .value { color: var(--signal); }
.stat .sub { .stat .sub {
margin-top: 4px; min-width: 0;
font-size: 12px; font-size: 12px;
color: var(--muted); color: var(--muted);
white-space: nowrap; white-space: nowrap;
@ -507,16 +515,17 @@ body > footer .meta:empty { display: none; }
} }
.weather-empty { grid-column: 1 / -1; padding: 18px; color: var(--muted); } .weather-empty { grid-column: 1 / -1; padding: 18px; color: var(--muted); }
/* kept short so the board below gets the height */
.weather-now { .weather-now {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 6px;
padding: 10px 22px 10px 8px; padding: 6px 20px 6px 8px;
} }
.weather-now img { width: 84px; height: 84px; margin: -6px 0; } .weather-now img { width: 56px; height: 56px; margin: -4px 0; }
.weather-place { .weather-place {
margin: 0 0 4px; margin: 0;
font-size: 15px; font-size: 14px;
font-weight: 600; font-weight: 600;
} }
.weather-place .note { .weather-place .note {
@ -526,19 +535,22 @@ body > footer .meta:empty { display: none; }
font-weight: 400; font-weight: 400;
color: var(--muted); color: var(--muted);
} }
.weather-reading { display: flex; align-items: baseline; gap: 10px; white-space: nowrap; }
.weather-temp { .weather-temp {
font-family: var(--mono); font-family: var(--mono);
font-size: 38px; font-size: 26px;
font-weight: 600; font-weight: 600;
line-height: 1; line-height: 1.15;
} }
.weather-desc { margin-top: 4px; font-size: 14px; text-transform: capitalize; } .weather-desc { font-size: 14px; text-transform: capitalize; }
/* two by two, so the details take two lines instead of four */
.weather-details { .weather-details {
display: flex; display: grid;
flex-direction: column; grid-template-columns: auto auto;
justify-content: center; align-content: center;
gap: 2px; justify-content: start;
padding: 10px 22px; gap: 2px 18px;
padding: 6px 20px;
border-left: 1px solid var(--rule); border-left: 1px solid var(--rule);
font-family: var(--mono); font-family: var(--mono);
font-size: 13px; font-size: 13px;
@ -553,16 +565,21 @@ body > footer .meta:empty { display: none; }
border-left: 1px solid var(--rule); border-left: 1px solid var(--rule);
} }
.forecast-day { .forecast-day {
padding: 10px 4px 12px; display: flex;
text-align: center; flex-direction: column;
justify-content: center;
align-items: center;
padding: 6px 4px;
font-family: var(--mono); font-family: var(--mono);
font-size: 12px; font-size: 13px;
} }
.forecast-day + .forecast-day { border-left: 1px solid color-mix(in oklab, var(--rule), transparent 50%); } .forecast-day + .forecast-day { border-left: 1px solid color-mix(in oklab, var(--rule), transparent 50%); }
.forecast-day .name { color: var(--muted); font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; } .forecast-day .name { color: var(--muted); font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; }
.forecast-day img { display: block; width: 44px; height: 44px; margin: 0 auto; } /* icon beside the temperatures rather than above them */
.forecast-day .day-line { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
.forecast-day img { width: 32px; height: 32px; margin: -4px 0; }
.forecast-day .lo { color: var(--muted); } .forecast-day .lo { color: var(--muted); }
.forecast-day .pop { margin-top: 2px; font-size: 12px; color: var(--rain); } .forecast-day .pop { color: var(--rain); }
/* ---- temporary: unassigned verification list (not for prod) ---- */ /* ---- temporary: unassigned verification list (not for prod) ---- */
@ -713,7 +730,7 @@ button:disabled { opacity: 0.5; cursor: default; }
.wrap { padding: 20px 16px 40px; } .wrap { padding: 20px 16px 40px; }
body > footer { padding: 0 16px 24px; } body > footer { padding: 0 16px 24px; }
.stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.stat .value { font-size: 24px; } .stat .value { font-size: 22px; }
.actions { margin-left: 0; width: 100%; } .actions { margin-left: 0; width: 100%; }
th, td { padding: 8px 10px; } th, td { padding: 8px 10px; }
} }
@ -1018,15 +1035,14 @@ function renderStats(wl) {
const tile = (label, value, sub, accent) => `<div class="stat${accent ? ' accent' : ''}"> const tile = (label, value, sub, accent) => `<div class="stat${accent ? ' accent' : ''}">
<div class="label">${esc(label)}</div> <div class="label">${esc(label)}</div>
<div class="value">${esc(value)}</div> <div class="reading"><span class="value">${esc(value)}</span><span class="sub">${esc(sub || ' ')}</span></div>
<div class="sub">${esc(sub || ' ')}</div>
</div>`; </div>`;
const un = data.unassigned || {}; const un = data.unassigned || {};
const unLoaded = !!un.updatedAt && !un.error; const unLoaded = !!un.updatedAt && !un.error;
return '<div class="stats">' return '<div class="stats">'
+ tile('Unassigned requests', unLoaded ? un.count : '–', unLoaded ? 'checked ' + clockTime(un.updatedAt) + (un.truncated ? ', partial' : '') : '') + tile('Unassigned', unLoaded ? un.count : '–', unLoaded ? 'checked ' + clockTime(un.updatedAt) + (un.truncated ? ', partial' : '') : '')
+ tile('Open requests', loaded ? open : '–', top ? top.total + ' ' + top.name : '') + tile('Open requests', loaded ? open : '–', top ? top.total + ' ' + top.name : '')
+ tile('Closed today', loaded ? closedToday : '–', 'since midnight', true) + tile('Closed today', loaded ? closedToday : '–', 'since midnight', true)
+ tile('Closed last hour', lastHour, data.tickets[0] ? 'latest #' + data.tickets[0].id : 'none yet', true) + tile('Closed last hour', lastHour, data.tickets[0] ? 'latest #' + data.tickets[0].id : 'none yet', true)
@ -1139,9 +1155,10 @@ function renderWeather() {
const today = new Date().toLocaleDateString('en-CA'); // YYYY-MM-DD in local time const today = new Date().toLocaleDateString('en-CA'); // YYYY-MM-DD in local time
const days = (weather.forecast || []).map(d => `<div class="forecast-day"> const days = (weather.forecast || []).map(d => `<div class="forecast-day">
<div class="name">${d.date === today ? 'Today' : new Date(d.date + 'T12:00').toLocaleDateString([], { weekday: 'short' })}</div> <div class="name">${d.date === today ? 'Today' : new Date(d.date + 'T12:00').toLocaleDateString([], { weekday: 'short' })}</div>
<div class="day-line">
${d.icon ? `<img src="${iconUrl(d.icon, 2)}" alt="${esc(d.condition || '')}">` : ''} ${d.icon ? `<img src="${iconUrl(d.icon, 2)}" alt="${esc(d.condition || '')}">` : ''}
<div>${d.high}° <span class="lo">${d.low}°</span></div> <span>${d.high}° <span class="lo">${d.low}°</span>${d.pop >= 20 ? ` <span class="pop">${d.pop}%</span>` : ''}</span>
<div class="pop">${d.pop >= 20 ? d.pop + '%' : '&nbsp;'}</div> </div>
</div>`).join(''); </div>`).join('');
const note = (weather.error ? 'stale · ' : '') + 'as of ' + clockTime(weather.updatedAt); const note = (weather.error ? 'stale · ' : '') + 'as of ' + clockTime(weather.updatedAt);
@ -1151,8 +1168,10 @@ function renderWeather() {
${weather.icon ? `<img src="${iconUrl(weather.icon, 4)}" alt="">` : '<span></span>'} ${weather.icon ? `<img src="${iconUrl(weather.icon, 4)}" alt="">` : '<span></span>'}
<div> <div>
<h2 class="weather-place">${esc(weather.place || 'Weather')} <span class="note">${esc(note)}</span></h2> <h2 class="weather-place">${esc(weather.place || 'Weather')} <span class="note">${esc(note)}</span></h2>
<div class="weather-temp">${weather.temp}°F</div> <div class="weather-reading">
<div class="weather-desc">${esc(weather.description || weather.condition || '')}</div> <span class="weather-temp">${weather.temp}°F</span>
<span class="weather-desc">${esc(weather.description || weather.condition || '')}</span>
</div>
</div> </div>
</div> </div>
<div class="weather-details"> <div class="weather-details">