diff --git a/CLAUDE.md b/CLAUDE.md index add112a..ebd9d3a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,6 +32,12 @@ title-bar controls). The rules that matter most: and bump the module's `version` on every change. - Browsers never call upstream services directly; the module's server routes proxy everything (no CORS, secrets stay server-side). +- **Keep the interface clean** (guide rules 5-11): no `help` and no label + over three words in a tile's gear; server-side settings in `configSchema` + only; a tile setting changes that tile alone; hide-toggles ungrouped when + there are four or fewer, in a folded **Show** group when there are five or + more; title-bar controls are icons with tooltips, never labels, and never + repeat something the gear already offers. - Style with the shell's CSS variables only, scoped under `[data-module=""]`; survive upstream loss (reconnect + `setStatus`). - Verify against the guide's testing section and resilience checklist. Mocks diff --git a/docs/MODULE-GUIDE.md b/docs/MODULE-GUIDE.md index da69532..33e5384 100644 --- a/docs/MODULE-GUIDE.md +++ b/docs/MODULE-GUIDE.md @@ -34,6 +34,46 @@ people running the booth depend on them. The admin page refuses to install a module whose requirement the running shell doesn't meet, and it offers updates by comparing versions. +### Keeping the interface clean + +A dashboard is read across a booth, at a glance, by someone doing three +other things. These rules are what keep every tile looking like one product +instead of six. They are as binding as the four above. + +5. **No explanatory text in a tile's gear.** No `help` on an + `instanceSchema` field or an `instanceGroups` entry. Labels are **three + words at most, one or two preferred** — "Slide count", "Item tags", + "Running clock". A control that cannot be understood in three words is + not a display choice; it belongs in `configSchema`, where `help` is + welcome and expected. *One exception:* a `template` field may keep a + single `help` line naming its placeholders, which nobody can guess. +6. **Server-side settings are admin-only.** Anything the module's server + acts on — a host, a credential, what to fetch, how often to poll — is + `configSchema`. `instanceSchema` is display choices and nothing else: + what this tile shows, and how big. +7. **A tile setting changes that tile and nothing else.** Never another + tile, never the module as a whole, never the server. If two tiles must + agree on something, it is either admin config or page state the shell + owns — not a setting each tile keeps its own copy of. +8. **Offer toggles for the elements you draw.** Every tile should let the + operator switch off the parts it does not need. **One to four toggles:** + give them no `group` at all, so they sit unlabelled at the top of the + gear. **Five or more:** one group named **Show**, `collapsed: true`, + `columns: 2`. Nothing else goes in that group — colours, formats and + text belong in groups of their own. +9. **Title-bar controls are icons.** No `label` on `addButton` or + `addMenu`; the tooltip carries the words, and `docs` above covers drawing + the icon. A small state indicator may ride alongside an icon — ProdCom's + channel button shows a count while a filter is on — but a control is + never named in the bar. +10. **If a control needs words to explain it, it is not a title-bar + control.** Put it in the gear. The bar is for actions whose icon and + tooltip say everything: refresh, clear, jump to latest, bigger text. +11. **One control per action.** Whatever has a dedicated title-bar button is + not offered again in the gear, and the reverse. Text size is A− / A+ up + there, never a "Text size" field; a timestamps button in the bar means + no timestamps switch below it. + ## The shape of a module A module is one folder dropped into `modules/`: @@ -177,14 +217,18 @@ the group name: `help` on a field puts one line of guidance under its control — allowed placeholders, where a value comes from. Keep labels short and put the -explanation there. +explanation there. **This is for `configSchema` only**: a tile's gear +carries no explanations at all (rule 5), so a setting that needs one is +telling you it belongs in admin. -`group` and `help` work on `instanceSchema` fields too, and a manifest +`group` works on `instanceSchema` fields too, and a manifest `instanceGroups` map plays the `configGroups` role for the tile's gear -popover (`collapsed: true | false`, `columns`, `help`, `toggle`). That is how -a module with a long show/hide checklist keeps the popover usable — see -`pco-plan`. Display choices belong there, per tile; admin config is for -connections. The popover itself is a small window beside the tile — sized to +popover (`collapsed: true | false`, `columns`, `toggle` — but not `help`, +per rule 5). That is how a module with a long show/hide checklist keeps the +popover usable: five or more hide-toggles go in one **Show** group, folded +by default, and fewer than five go in no group at all — see `pco-plan` for +the long form and `slack` for the short one. Display choices belong there, +per tile; admin config is for connections. The popover itself is a small window beside the tile — sized to its content, moved by the slim bar on top, closed by clicking anywhere else or pressing Escape — so a tile never has to be big enough to hold its own settings, and a change can be watched as it lands. Under everything you @@ -354,17 +398,19 @@ menu.setLabel('Channels (2/4) ▾'); // update the button text any time ``` Everything you add is removed automatically when your instance stops, so a -remount never duplicates controls. Keep them compact (icon buttons, short -labels): the title bar is 32 px tall and shared with the tile's name. Draw -icons as inline SVG rather than characters — a glyph such as ✕ or + takes -its size from whatever font each browser picks; text-size buttons are a -drawn − and +. +remount never duplicates controls. **Icons only, no labels** (rule 9): the +title bar is 32 px tall and shared with the tile's name, and the tooltip +says what the icon means. Draw icons as inline SVG rather than characters — +a glyph such as ✕ or + takes its size from whatever font each browser +picks; text-size buttons are a drawn − and +. A small state indicator may +ride alongside an icon (ProdCom's channel button carries a count while a +filter is on); a name may not. -**One control per action.** Whatever has a dedicated header button is not -offered again in the gear popover: text size is A− / A+ up here (persisted -through `saveInstanceSettings({ textSize })`, read back with a sane -fallback), never a "Text size" field in `instanceSchema`; a timestamps or -newest-first toggle in the header means no such switch in the gear. The +**One control per action** (rule 11). Whatever has a dedicated header button +is not offered again in the gear popover: text size is − / + up here +(persisted through `saveInstanceSettings({ textSize })`, read back with a +sane fallback), never a "Text size" field in `instanceSchema`; a timestamps +or newest-first toggle in the header means no such switch in the gear. The gear is for display choices that have no button of their own. One more thing to know: the whole title bar folds away when the dashboard diff --git a/modules/pco-plan/module.json b/modules/pco-plan/module.json index 7899921..e266ffa 100644 --- a/modules/pco-plan/module.json +++ b/modules/pco-plan/module.json @@ -1,15 +1,23 @@ { "id": "pco-plan", "name": "PCO Plan", - "version": "1.4.0", + "version": "1.5.0", "proddash": ">=1.4.0", "description": "Order of service from Planning Center Services", - "provides": ["timers"], + "provides": [ + "timers" + ], "client": "client.js", "server": "server.js", "style": "style.css", - "minSize": { "w": 3, "h": 3 }, - "defaultSize": { "w": 5, "h": 6 }, + "minSize": { + "w": 3, + "h": 3 + }, + "defaultSize": { + "w": 5, + "h": 6 + }, "configGroups": { "Planning Center credentials": { "collapsed": "whenSet", @@ -48,7 +56,12 @@ "type": "select", "label": "Plan", "default": "", - "options": [{ "value": "", "label": "Automatic — today's plan, else the next upcoming" }], + "options": [ + { + "value": "", + "label": "Automatic — today's plan, else the next upcoming" + } + ], "optionsRoute": "/plans", "optionsDependsOn": "serviceTypeId" }, @@ -82,8 +95,14 @@ "group": "Service timing", "showWhen": "useDocumentTriggers", "options": [ - { "value": "schedule", "label": "Scheduled service time" }, - { "value": "document", "label": "ProPresenter document" } + { + "value": "schedule", + "label": "Scheduled service time" + }, + { + "value": "document", + "label": "ProPresenter document" + } ] }, "serviceStartDocument": { @@ -124,37 +143,94 @@ "Item notes": { "toggle": "showNotes" }, - "Show / hide": { + "Show": { "collapsed": true, - "columns": 3, - "help": "Switch off anything this tile should not show." + "columns": 2 } }, "instanceSchema": { - "autoScroll": { "type": "boolean", "label": "Keep the current item in view", "default": true }, + "autoScroll": { + "type": "boolean", + "label": "Auto-scroll", + "default": true + }, "headerTemplate": { "type": "template", "label": "Top bar text", "default": "{series} • {part} | {serviceType} • {date}", "group": "Top bar", "variables": [ - { "value": "{series}", "label": "Series" }, - { "value": "{part}", "label": "Part / plan title" }, - { "value": "{serviceType}", "label": "Service type" }, - { "value": "{date}", "label": "Date" }, - { "value": "{shortDate}", "label": "Short date" }, - { "value": "{time}", "label": "Service time" }, - { "value": "{length}", "label": "Planned length" }, - { "value": "{items}", "label": "Item count" } + { + "value": "{series}", + "label": "Series" + }, + { + "value": "{part}", + "label": "Part / plan title" + }, + { + "value": "{serviceType}", + "label": "Service type" + }, + { + "value": "{date}", + "label": "Date" + }, + { + "value": "{shortDate}", + "label": "Short date" + }, + { + "value": "{time}", + "label": "Service time" + }, + { + "value": "{length}", + "label": "Planned length" + }, + { + "value": "{items}", + "label": "Item count" + } ], "help": "Use • between items and | to start a second line." }, - "keyColor": { "type": "color", "label": "Song key", "default": "#4ea1ff", "group": "Colors" }, - "headerColor": { "type": "color", "label": "Section headers", "default": "#2ee59a", "group": "Colors" }, - "currentColor": { "type": "color", "label": "Current item", "default": "#2ee59a", "group": "Colors" }, - "headerGradient": { "type": "boolean", "label": "Fade header color to the right", "default": true, "group": "Colors" }, - "tintByType": { "type": "boolean", "label": "Tint items by type (song / media / other)", "default": false, "group": "Colors" }, - "showNotes": { "type": "switch", "label": "Item notes", "default": true, "group": "Item notes" }, + "keyColor": { + "type": "color", + "label": "Song key", + "default": "#4ea1ff", + "group": "Colors" + }, + "headerColor": { + "type": "color", + "label": "Section headers", + "default": "#2ee59a", + "group": "Colors" + }, + "currentColor": { + "type": "color", + "label": "Current item", + "default": "#2ee59a", + "group": "Colors" + }, + "headerGradient": { + "type": "boolean", + "label": "Header fade", + "default": true, + "group": "Colors" + }, + "tintByType": { + "type": "boolean", + "label": "Tint by type", + "default": false, + "group": "Colors" + }, + "showNotes": { + "type": "switch", + "label": "Item notes", + "default": true, + "group": "Item notes" + }, "noteCategories": { "type": "multiselect", "label": "Categories", @@ -163,29 +239,149 @@ "columns": 3, "allByDefault": true }, - "showTopBar": { "type": "boolean", "label": "Top bar", "default": true, "group": "Show / hide" }, - "showSeries": { "type": "boolean", "label": "Series name", "default": true, "group": "Show / hide" }, - "showPart": { "type": "boolean", "label": "Part # / plan title", "default": true, "group": "Show / hide" }, - "showServiceType": { "type": "boolean", "label": "Service type", "default": true, "group": "Show / hide" }, - "showDate": { "type": "boolean", "label": "Plan date", "default": true, "group": "Show / hide" }, - "showServiceTime": { "type": "boolean", "label": "Service time", "default": true, "group": "Show / hide" }, - "showTotalLength": { "type": "boolean", "label": "Planned total length", "default": true, "group": "Show / hide" }, - "showRunningClock": { "type": "boolean", "label": "Running clock (ahead / behind)", "default": true, "group": "Show / hide" }, - "showCountdown": { "type": "boolean", "label": "Countdown to service start", "default": true, "group": "Show / hide" }, - "showHeaders": { "type": "boolean", "label": "Section headers", "default": true, "group": "Show / hide" }, - "showItemNumbers": { "type": "boolean", "label": "Item numbers", "default": false, "group": "Show / hide" }, - "showStartTimes": { "type": "boolean", "label": "Scheduled start times", "default": true, "group": "Show / hide" }, - "showLength": { "type": "boolean", "label": "Item length", "default": true, "group": "Show / hide" }, - "showItemType": { "type": "boolean", "label": "Item type badge", "default": true, "group": "Show / hide" }, - "showKey": { "type": "boolean", "label": "Song key", "default": true, "group": "Show / hide" }, - "showArrangement": { "type": "boolean", "label": "Song arrangement", "default": false, "group": "Show / hide" }, - "showSongAuthor": { "type": "boolean", "label": "Song author / CCLI", "default": false, "group": "Show / hide" }, - "showDescription": { "type": "boolean", "label": "Item description", "default": true, "group": "Show / hide" }, - "showPreService": { "type": "boolean", "label": "Pre-service items", "default": true, "group": "Show / hide" }, - "showPostService": { "type": "boolean", "label": "Post-service items", "default": true, "group": "Show / hide" }, - "showNowIndicator": { "type": "boolean", "label": "Now indicator", "default": true, "group": "Show / hide" }, - "showItemRuntime": { "type": "boolean", "label": "Item runtime (elapsed vs planned)", "default": true, "group": "Show / hide" }, - "dimCompleted": { "type": "boolean", "label": "Dim completed items", "default": true, "group": "Show / hide" }, - "showLiveFooter": { "type": "boolean", "label": "ProPresenter match line", "default": true, "group": "Show / hide" } + "showTopBar": { + "type": "boolean", + "label": "Top bar", + "default": true, + "group": "Show" + }, + "showSeries": { + "type": "boolean", + "label": "Series name", + "default": true, + "group": "Show" + }, + "showPart": { + "type": "boolean", + "label": "Part / title", + "default": true, + "group": "Show" + }, + "showServiceType": { + "type": "boolean", + "label": "Service type", + "default": true, + "group": "Show" + }, + "showDate": { + "type": "boolean", + "label": "Plan date", + "default": true, + "group": "Show" + }, + "showServiceTime": { + "type": "boolean", + "label": "Service time", + "default": true, + "group": "Show" + }, + "showTotalLength": { + "type": "boolean", + "label": "Total length", + "default": true, + "group": "Show" + }, + "showRunningClock": { + "type": "boolean", + "label": "Running clock", + "default": true, + "group": "Show" + }, + "showCountdown": { + "type": "boolean", + "label": "Countdown", + "default": true, + "group": "Show" + }, + "showHeaders": { + "type": "boolean", + "label": "Section headers", + "default": true, + "group": "Show" + }, + "showItemNumbers": { + "type": "boolean", + "label": "Item numbers", + "default": false, + "group": "Show" + }, + "showStartTimes": { + "type": "boolean", + "label": "Start times", + "default": true, + "group": "Show" + }, + "showLength": { + "type": "boolean", + "label": "Item length", + "default": true, + "group": "Show" + }, + "showItemType": { + "type": "boolean", + "label": "Type badge", + "default": true, + "group": "Show" + }, + "showKey": { + "type": "boolean", + "label": "Song key", + "default": true, + "group": "Show" + }, + "showArrangement": { + "type": "boolean", + "label": "Arrangement", + "default": false, + "group": "Show" + }, + "showSongAuthor": { + "type": "boolean", + "label": "Author / CCLI", + "default": false, + "group": "Show" + }, + "showDescription": { + "type": "boolean", + "label": "Item description", + "default": true, + "group": "Show" + }, + "showPreService": { + "type": "boolean", + "label": "Pre-service", + "default": true, + "group": "Show" + }, + "showPostService": { + "type": "boolean", + "label": "Post-service", + "default": true, + "group": "Show" + }, + "showNowIndicator": { + "type": "boolean", + "label": "Now indicator", + "default": true, + "group": "Show" + }, + "showItemRuntime": { + "type": "boolean", + "label": "Item runtime", + "default": true, + "group": "Show" + }, + "dimCompleted": { + "type": "boolean", + "label": "Dim completed", + "default": true, + "group": "Show" + }, + "showLiveFooter": { + "type": "boolean", + "label": "Match line", + "default": true, + "group": "Show" + } } } diff --git a/modules/prodcom-transcript/module.json b/modules/prodcom-transcript/module.json index 2fecb62..170610a 100644 --- a/modules/prodcom-transcript/module.json +++ b/modules/prodcom-transcript/module.json @@ -1,7 +1,7 @@ { "id": "prodcom-transcript", "name": "ProdCom", - "version": "1.3.1", + "version": "1.3.2", "proddash": ">=1.1.0", "description": "Live ProdCom transcript", "permissions": [ @@ -40,7 +40,7 @@ "instanceSchema": { "showAvatars": { "type": "boolean", - "label": "Show channel icons", + "label": "Channel icons", "default": true } } diff --git a/modules/propresenter-now-next/module.json b/modules/propresenter-now-next/module.json index 67d188e..2b02e79 100644 --- a/modules/propresenter-now-next/module.json +++ b/modules/propresenter-now-next/module.json @@ -1,22 +1,34 @@ { "id": "propresenter-now-next", "name": "ProPresenter", - "version": "1.1.0", + "version": "1.1.1", "proddash": ">=1.5.0", "description": "What is live in ProPresenter and what comes next", "permissions": [ - { "kind": "local-network", "reason": "Reaching ProPresenter on this network" } + { + "kind": "local-network", + "reason": "Reaching ProPresenter on this network" + } ], "client": "client.js", "server": "server.js", "style": "style.css", - "minSize": { "w": 3, "h": 3 }, - "defaultSize": { "w": 4, "h": 5 }, + "minSize": { + "w": 3, + "h": 3 + }, + "defaultSize": { + "w": 4, + "h": 5 + }, "configSchema": { "propresenter": { "type": "endpoint", "label": "ProPresenter", - "default": { "host": "", "port": 1025 } + "default": { + "host": "", + "port": 1025 + } }, "password": { "type": "password", @@ -25,17 +37,64 @@ } }, "instanceSchema": { - "cleanTitles": { "type": "switch", "label": "Tidy titles", "default": true }, - "showPlaylist": { "type": "switch", "label": "Playlist", "default": true, "group": "Show" }, - "showLive": { "type": "switch", "label": "Live status", "default": true, "group": "Show" }, - "showNowLabel": { "type": "switch", "label": "Now label", "default": true, "group": "Show" }, - "showSlideCount": { "type": "switch", "label": "Slide count", "default": true, "group": "Show" }, - "showProgress": { "type": "switch", "label": "Progress bar", "default": true, "group": "Show" }, - "showNext": { "type": "switch", "label": "Next list", "default": true, "group": "Show" }, - "showNextLabel": { "type": "switch", "label": "Next label", "default": true, "group": "Show" }, - "showTags": { "type": "switch", "label": "Item tags", "default": true, "group": "Show" } + "cleanTitles": { + "type": "switch", + "label": "Tidy titles", + "default": true + }, + "showPlaylist": { + "type": "switch", + "label": "Playlist", + "default": true, + "group": "Show" + }, + "showLive": { + "type": "switch", + "label": "Live status", + "default": true, + "group": "Show" + }, + "showNowLabel": { + "type": "switch", + "label": "Now label", + "default": true, + "group": "Show" + }, + "showSlideCount": { + "type": "switch", + "label": "Slide count", + "default": true, + "group": "Show" + }, + "showProgress": { + "type": "switch", + "label": "Progress bar", + "default": true, + "group": "Show" + }, + "showNext": { + "type": "switch", + "label": "Next list", + "default": true, + "group": "Show" + }, + "showNextLabel": { + "type": "switch", + "label": "Next label", + "default": true, + "group": "Show" + }, + "showTags": { + "type": "switch", + "label": "Item tags", + "default": true, + "group": "Show" + } }, "instanceGroups": { - "Show": { "collapsed": false, "columns": 2 } + "Show": { + "collapsed": true, + "columns": 2 + } } } diff --git a/modules/propresenter-now-next/server.js b/modules/propresenter-now-next/server.js index e13503e..bf7048a 100644 --- a/modules/propresenter-now-next/server.js +++ b/modules/propresenter-now-next/server.js @@ -140,7 +140,7 @@ module.exports = { showSlideCount: { type: 'switch', label: 'Slide count', default: true, group: 'Show' }, showProgress: { type: 'switch', label: 'Progress bar', default: true, group: 'Show' }, }, - instanceGroups: { Show: { collapsed: false, columns: 2 } }, + instanceGroups: { Show: { collapsed: true, columns: 2 } }, }, ]; }, diff --git a/modules/propresenter-timers/client.js b/modules/propresenter-timers/client.js index 5223b40..c2c10f8 100644 --- a/modules/propresenter-timers/client.js +++ b/modules/propresenter-timers/client.js @@ -15,7 +15,7 @@ Per-tile choices: the gear popover's static switches (whole sources, headings, status lines, the clock's format, the state colours) and the - "Timers ▾" header menu, which hides any single timer — keyed `source:id` + timers header menu, which hides any single timer — keyed `source:id` and saved with the layout via saveInstanceSettings. The header can be hidden by the user, so the static switches stay the primary controls. A solo tile's gear carries only the settings its one card can use (the @@ -196,6 +196,10 @@ function defaultStatus(timer, value, tone) { /* ── the tile ─────────────────────────────────────────────────────────── */ +/* The header menu's icon: a stopwatch, for the list of timers behind it. + Title-bar controls carry no words (module guide, rule 9). */ +const TIMERS_SVG = ''; + export default function create({ root, moduleApi }) { let state = null; let feedOffline = false; @@ -227,7 +231,7 @@ export default function create({ root, moduleApi }) { } /* Which picker entry this tile was added as (the module's tiles() list): - '' or 'all' = every timer, selectable via the Timers ▾ menu; + '' or 'all' = every timer, selectable via the timers menu; 'solo::' = exactly one card, no menu, no headings. The earlier entries 'timer:' and 'ltc' map onto the new keys. */ const variant = String(moduleApi.variant || ''); @@ -283,8 +287,8 @@ export default function create({ root, moduleApi }) { let menuEl = null; // the open menu's element (filled on each open) const itemsMenu = solo ? null : moduleApi.header.addMenu({ - label: 'Timers ▾', - title: 'Choose timers', + icon: TIMERS_SVG, + title: 'Timers', build(menu) { // the shell hands over an emptied element on every open menu.classList.add('tm-menu'); @@ -292,6 +296,12 @@ export default function create({ root, moduleApi }) { rebuildMenu(true); }, }); + // The icon carries the name; a count rides alongside it while some timers + // are hidden, so the bar says "filtered" without saying it in words. + const itemsCountEl = document.createElement('span'); + itemsCountEl.className = 'tm-count'; + itemsCountEl.hidden = true; + if (itemsMenu) itemsMenu.button.appendChild(itemsCountEl); function menuRows() { const rows = []; @@ -319,7 +329,9 @@ export default function create({ root, moduleApi }) { if (!itemsMenu) return; const rows = menuRows(); const visible = rows.filter((r) => !hidden.has(r.key)).length; - itemsMenu.setLabel(visible === rows.length ? 'Timers ▾' : `Timers (${visible}/${rows.length}) ▾`); + const filtering = visible !== rows.length; + itemsCountEl.textContent = filtering ? `${visible}/${rows.length}` : ''; + itemsCountEl.hidden = !filtering; } let menuSignature = ''; @@ -779,7 +791,7 @@ export default function create({ root, moduleApi }) { let hint = ''; const groups = visibleGroups(); if (state && !state.sources.length) hint = 'Every source is switched off — turn one on in Admin.'; - else if (state && !groups.length) hint = 'Nothing selected — switch a source on in this tile’s settings, or pick timers in the Timers ▾ menu.'; + else if (state && !groups.length) hint = 'Nothing selected — switch a source on in this tile’s settings, or pick timers in the title bar’s timers menu.'; hintEl.hidden = !hint; hintEl.textContent = hint; groupsEl.hidden = Boolean(hint); diff --git a/modules/propresenter-timers/module.json b/modules/propresenter-timers/module.json index 683de2b..728c444 100644 --- a/modules/propresenter-timers/module.json +++ b/modules/propresenter-timers/module.json @@ -1,7 +1,7 @@ { "id": "propresenter-timers", "name": "Timers", - "version": "1.3.0", + "version": "1.4.0", "proddash": ">=1.4.0", "description": "Timers, Clocks, and LTC From configured Modules", "provides": [ @@ -112,7 +112,7 @@ }, "instanceGroups": { "Show": { - "collapsed": false, + "collapsed": true, "columns": 2 }, "Clock": { @@ -121,8 +121,7 @@ }, "Colours": { "collapsed": true, - "columns": 2, - "help": "The time's colour per state. Left at the default, a colour follows the dashboard theme; one you change here wins over it." + "columns": 2 } }, "instanceSchema": { diff --git a/modules/propresenter-timers/style.css b/modules/propresenter-timers/style.css index a216138..f1b13a8 100644 --- a/modules/propresenter-timers/style.css +++ b/modules/propresenter-timers/style.css @@ -321,7 +321,7 @@ @keyframes tm-nosignal-blink { 50% { opacity: 0.35; } } -/* ── the "Timers ▾" menu (container styled/positioned by the shell) ── */ +/* ── the timers menu (container styled/positioned by the shell) ── */ [data-module="propresenter-timers"] .tm-menu-item { display: flex; @@ -383,3 +383,11 @@ font-size: 12px; white-space: nowrap; } + +/* the count beside the title-bar timers icon, while some are hidden */ +[data-module="propresenter-timers"] .tm-count { + font-size: 10px; + font-weight: 700; + font-variant-numeric: tabular-nums; +} +[data-module="propresenter-timers"] .tm-count[hidden] { display: none; }