Timers: admin "Timer font" option with a monospace choice

A new Display-group select (Default / Monospace) sets the font for the
timer and LTC digits. Monospace keeps the numbers from shifting sideways
as they change — easier to read on a fast-moving countdown or timecode.
Flows through server state → client applies a `tm-mono` class and re-fits
the digit sizing; both timer and LTC values (.tm-time) honor it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Waters Church
2026-09-10 15:23:07 -04:00
co-authored by Claude Opus 4.8
parent 1d46657e06
commit 5fe39539fc
5 changed files with 33 additions and 4 deletions
+3 -2
View File
@@ -176,9 +176,10 @@ below. Nothing is installed without someone clicking.
discovered timer (and the LTC card) as its own tile, or add "All timers" discovered timer (and the LTC card) as its own tile, or add "All timers"
and pick a selection via the tile's "Timers ▾" menu; overruns are and pick a selection via the tile's "Timers ▾" menu; overruns are
unmistakably red. Read-only — no start/stop controls. Admin config is in unmistakably red. Read-only — no start/stop controls. Admin config is in
two groups: **ProPresenter** (endpoint host : port, API password) and three groups: **ProPresenter** (endpoint host : port, API password),
**LTC Timecode** (the listener switch, and — when it's on — the audio **LTC Timecode** (the listener switch, and — when it's on — the audio
device and channel). device and channel), and **Display** (Timer font — Default or Monospace,
which keeps the digits from shifting as the numbers change).
ProPresenter exposes no timecode over any API, so LTC is decoded from the ProPresenter exposes no timecode over any API, so LTC is decoded from the
audio signal, behind the admin **LTC listener** switch (off ⇒ no LTC tile audio signal, behind the admin **LTC listener** switch (off ⇒ no LTC tile
+8
View File
@@ -393,6 +393,14 @@ export default function create({ root, moduleApi }) {
rebuildMenu(); rebuildMenu();
updateLtcAnchor(); updateLtcAnchor();
// Admin-chosen digit font. A font swap changes glyph metrics, so re-fit
// the digit sizing when it actually changes.
const mono = state?.timerFont === 'monospace';
if (mono !== wrap.classList.contains('tm-mono')) {
wrap.classList.toggle('tm-mono', mono);
fit();
}
let hint = ''; let hint = '';
if (state && !state.enabled && !state.ltc?.supported) { if (state && !state.enabled && !state.ltc?.supported) {
hint = 'No ProPresenter connection — set the host and port in Admin.'; hint = 'No ProPresenter connection — set the host and port in Admin.';
+10
View File
@@ -42,6 +42,16 @@
"group": "LTC Timecode", "group": "LTC Timecode",
"showWhen": "ltcEnabled", "showWhen": "ltcEnabled",
"default": 1 "default": 1
},
"timerFont": {
"type": "select",
"label": "Timer font",
"group": "Display",
"default": "default",
"options": [
{ "value": "default", "label": "Default (system)" },
{ "value": "monospace", "label": "Monospace — steadier changing digits" }
]
} }
} }
} }
+5 -2
View File
@@ -57,10 +57,11 @@ const { createLtcListener } = require('./ltc-listener');
/** Set by init(), read by the routes — both are rebuilt together on remount. */ /** Set by init(), read by the routes — both are rebuilt together on remount. */
let current = null; let current = null;
function clearedState(enabled, ltcEnabled = false) { function clearedState(enabled, ltcEnabled = false, timerFont = 'default') {
return { return {
enabled, enabled,
ltcEnabled, ltcEnabled,
timerFont,
reachable: false, reachable: false,
lastError: '', lastError: '',
timers: [], timers: [],
@@ -86,8 +87,9 @@ module.exports = {
const ltcEnabled = Boolean(config.ltcEnabled); const ltcEnabled = Boolean(config.ltcEnabled);
const ltcDevice = String(config.ltcDevice || ''); const ltcDevice = String(config.ltcDevice || '');
const ltcChannel = Math.max(1, Math.trunc(Number(config.ltcChannel)) || 1); const ltcChannel = Math.max(1, Math.trunc(Number(config.ltcChannel)) || 1);
const timerFont = config.timerFont === 'monospace' ? 'monospace' : 'default';
let latest = clearedState(enabled, ltcEnabled); let latest = clearedState(enabled, ltcEnabled, timerFont);
let lastFrame = ''; let lastFrame = '';
let stopped = false; let stopped = false;
let pollTimer = null; let pollTimer = null;
@@ -207,6 +209,7 @@ module.exports = {
latest = { latest = {
enabled, enabled,
ltcEnabled, ltcEnabled,
timerFont,
reachable: latest.reachable, reachable: latest.reachable,
lastError: latest.lastError, lastError: latest.lastError,
timers: latest.timers, timers: latest.timers,
+7
View File
@@ -113,6 +113,13 @@
white-space: nowrap; white-space: nowrap;
} }
/* Admin "Timer font: Monospace" — steadier digits as the numbers change.
Applies to timer and LTC values alike (both are .tm-time). */
[data-module="propresenter-timers"] .tm-mono .tm-time {
font-family: ui-monospace, "SF Mono", "Menlo", "Cascadia Mono", "Consolas", monospace;
letter-spacing: 0;
}
[data-module="propresenter-timers"] .tm-state { [data-module="propresenter-timers"] .tm-state {
flex: none; flex: none;
max-width: 100%; max-width: 100%;