Files
ProdDash/modules/propresenter-timers/style.css
T
Waters ChurchandClaude Fable 5.1 1db6c2c89f Timers: "to 9:30a" under a countdown-to-time, smaller status lines, titles that fit
A ProPresenter count_down_to_time timer's detail line reads "to 9:30a"
(or "to 21:30" on a 24-hour clock) instead of a sentence. The status and
detail lines under the digits are drawn at 0.8 of the title's size, and
fit() budgets them at that share. A title that would be cut off shrinks
to fit its card (measured off-screen, down to 8 px) instead of ending in
an ellipsis; a changed label re-fits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 09:05:56 -04:00

355 lines
11 KiB
CSS

/* Timers module — scoped to its tiles, shell variables only.
A card's colour IS its state: running / warning / overrun / idle. The four
tones are custom properties on the tile root: they default to the shell's
palette (so a dashboard theme reaches them) and the tile's own colour
settings, when changed, are set inline on .tm-wrap and win — the
documented mechanism. Overrun must be unmistakable across the room:
danger fill + pulse. */
[data-module="propresenter-timers"] .tm-wrap {
--tm-ok: var(--accent);
--tm-warn: var(--warn);
--tm-danger: var(--danger);
--tm-muted: var(--muted);
--tm-meta-size: 11px;
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
user-select: none;
-webkit-user-select: none;
}
/* ── degraded-state banner (the ProdDash feed itself is down) ── */
[data-module="propresenter-timers"] .tm-banner {
flex: none;
margin: 6px 8px 0;
padding: 4px 10px;
border-radius: 6px;
background: color-mix(in srgb, var(--danger) 14%, transparent);
color: var(--danger);
font-size: 12px;
font-weight: 600;
text-align: center;
}
[data-module="propresenter-timers"] .tm-banner[hidden] { display: none; }
/* Values still on screen while their source is down are stale — say so
visually, per group: the LTC card keeps ticking when only ProPresenter
is unreachable. When the ProdDash feed is down everything is old. */
[data-module="propresenter-timers"] .tm-group.is-stale .tm-card,
[data-module="propresenter-timers"] .tm-wrap.is-feed-down .tm-card { opacity: 0.45; }
[data-module="propresenter-timers"] .tm-hint {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 12px 16px;
color: var(--muted);
font-size: 13px;
text-align: center;
}
[data-module="propresenter-timers"] .tm-hint[hidden] { display: none; }
/* ── source groups: heading + card grid (column count set by fit()) ── */
[data-module="propresenter-timers"] .tm-groups {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: var(--tm-gap, 8px);
padding: var(--tm-pad, 10px);
}
[data-module="propresenter-timers"] .tm-groups[hidden] { display: none; }
[data-module="propresenter-timers"] .tm-group {
min-height: 0;
display: flex;
flex-direction: column;
flex: 1 1 0;
}
[data-module="propresenter-timers"] .tm-head {
flex: none;
display: flex;
align-items: baseline;
gap: 8px;
min-width: 0;
margin: 0 2px 4px;
font-size: 11px;
line-height: 16px;
color: var(--muted);
}
[data-module="propresenter-timers"] .tm-head[hidden] { display: none; }
[data-module="propresenter-timers"] .tm-title {
flex: none;
font-weight: 800;
letter-spacing: 0.1em;
text-transform: uppercase;
}
/* The source's own word while it isn't OK ("Unreachable at …"). */
[data-module="propresenter-timers"] .tm-note {
flex: 1;
min-width: 0;
font-weight: 500;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
[data-module="propresenter-timers"] .tm-group.is-down .tm-note { color: var(--tm-warn); }
[data-module="propresenter-timers"] .tm-group.is-stale .tm-note { color: var(--tm-danger); }
/* Headings off: a source in trouble, or with nothing to show, still says so. */
[data-module="propresenter-timers"] .tm-empty {
flex: none;
padding: 2px 4px;
color: var(--muted);
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
[data-module="propresenter-timers"] .tm-empty[hidden] { display: none; }
[data-module="propresenter-timers"] .tm-group.is-down .tm-empty { color: var(--tm-warn); }
[data-module="propresenter-timers"] .tm-grid {
flex: 1;
min-height: 0;
display: grid;
grid-auto-rows: 1fr;
gap: var(--tm-gap, 8px);
}
[data-module="propresenter-timers"] .tm-grid:empty { display: none; }
/* Compact cards (short tiles) and "Status lines" off: the lines under the
digits go, the colour stays. A note that explains a missing value (no
LTC input, a solo timer no longer offered) must never be hidden. */
[data-module="propresenter-timers"] .tm-compact .tm-status:not(.is-note),
[data-module="propresenter-timers"] .tm-compact .tm-detail,
[data-module="propresenter-timers"] .tm-nostatus .tm-status:not(.is-note),
[data-module="propresenter-timers"] .tm-nostatus .tm-detail { display: none; }
/* ── one timer card ── */
[data-module="propresenter-timers"] .tm-card {
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2%;
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: 10px;
background: color-mix(in srgb, var(--bg) 60%, transparent);
overflow: hidden;
transition: opacity 0.3s ease;
}
[data-module="propresenter-timers"] .tm-name {
flex: none;
max-width: 100%;
display: flex;
align-items: center;
gap: 0.5em;
font-size: var(--tm-meta-size);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
white-space: nowrap;
}
/* The label is its own box so a long name ends in an ellipsis (a flex
container's bare text won't). */
[data-module="propresenter-timers"] .tm-label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-module="propresenter-timers"] .tm-time {
flex: none;
font-size: 32px; /* fit() sets the real size per card */
font-weight: 700;
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
line-height: 1.05;
color: var(--text);
white-space: nowrap;
}
/* Admin "Timer font: Monospace" — steadier digits as the numbers change. */
[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-status,
[data-module="propresenter-timers"] .tm-detail {
flex: none;
max-width: 100%;
font-size: calc(var(--tm-meta-size) * 0.8); /* smaller than the title; fit() budgets the same share */
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
[data-module="propresenter-timers"] .tm-status[hidden],
[data-module="propresenter-timers"] .tm-detail[hidden] { display: none; }
[data-module="propresenter-timers"] .tm-detail {
font-weight: 600;
letter-spacing: 0.04em;
text-transform: none;
opacity: 0.85;
}
/* A provider's status line carries its own tone ("ahead 1:20" in green). */
[data-module="propresenter-timers"] .tm-status[data-tone="ok"] { color: var(--tm-ok); }
[data-module="propresenter-timers"] .tm-status[data-tone="warn"] { color: var(--tm-warn); }
[data-module="propresenter-timers"] .tm-status[data-tone="danger"] { color: var(--tm-danger); }
[data-module="propresenter-timers"] .tm-status[data-tone="muted"] { color: var(--tm-muted); }
/* Explanatory notes read as sentences, not badges. */
[data-module="propresenter-timers"] .tm-status.is-note {
text-transform: none;
letter-spacing: 0.02em;
white-space: normal;
text-align: center;
}
/* ── state looks (feedbacks) ── */
[data-module="propresenter-timers"] .tm-card.is-ok {
border-color: color-mix(in srgb, var(--tm-ok) 55%, var(--border));
background: color-mix(in srgb, var(--tm-ok) 10%, transparent);
}
[data-module="propresenter-timers"] .tm-card.is-ok .tm-time { color: var(--tm-ok); }
[data-module="propresenter-timers"] .tm-card.is-warn {
border-color: color-mix(in srgb, var(--tm-warn) 55%, var(--border));
background: color-mix(in srgb, var(--tm-warn) 10%, transparent);
}
[data-module="propresenter-timers"] .tm-card.is-warn .tm-time { color: var(--tm-warn); }
[data-module="propresenter-timers"] .tm-card.is-muted .tm-time { color: var(--tm-muted); }
/* The wall clock is information, not a state — plain text colour. */
[data-module="propresenter-timers"] .tm-card.is-plain .tm-time { color: var(--text); }
/* Overrun: readable across the room even on a wall display. */
[data-module="propresenter-timers"] .tm-card.is-danger {
border-color: var(--tm-danger);
background: color-mix(in srgb, var(--tm-danger) 22%, transparent);
}
[data-module="propresenter-timers"] .tm-card.is-danger .tm-time { color: var(--tm-danger); }
[data-module="propresenter-timers"] .tm-card.is-overrun {
animation: tm-overrun-pulse 1.2s ease-in-out infinite;
}
[data-module="propresenter-timers"] .tm-card.is-overrun .tm-status { font-weight: 800; }
@keyframes tm-overrun-pulse {
50% { background: color-mix(in srgb, var(--tm-danger) 38%, transparent); }
}
/* A solo tile whose timer is no longer offered. */
[data-module="propresenter-timers"] .tm-card.is-missing { border-style: dashed; }
/* ── the LTC card: visually its own thing ── */
[data-module="propresenter-timers"] .tm-card-ltc { border-style: dashed; }
[data-module="propresenter-timers"] .tm-ltc-badge {
flex: none;
padding: 0.15em 0.55em;
border-radius: 5px;
background: color-mix(in srgb, var(--tm-ok) 24%, transparent);
color: var(--text);
font-size: 0.95em;
letter-spacing: 0.12em;
}
/* No signal: muted digits, a blinking warning line. */
[data-module="propresenter-timers"] .tm-card.is-nosignal {
border-color: color-mix(in srgb, var(--tm-warn) 55%, var(--border));
}
[data-module="propresenter-timers"] .tm-card.is-nosignal .tm-status {
animation: tm-nosignal-blink 1.6s steps(2) infinite;
}
@keyframes tm-nosignal-blink { 50% { opacity: 0.35; } }
/* ── the "Timers ▾" menu (container styled/positioned by the shell) ── */
[data-module="propresenter-timers"] .tm-menu-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
width: 100%;
padding: 6px 9px;
background: none;
border: none;
border-radius: 6px;
color: var(--text);
font-size: 12px;
font-family: inherit;
text-align: left;
cursor: pointer;
white-space: nowrap;
}
[data-module="propresenter-timers"] .tm-menu-item:hover { background: var(--border); }
[data-module="propresenter-timers"] .tm-menu-item .name { font-weight: 600; }
[data-module="propresenter-timers"] .tm-menu-item:not(.on) .name { color: var(--muted); }
/* A small switch: off = hollow pill, on = filled with the accent. (The menu
hangs off the tile's title bar, outside .tm-wrap, so the tile's own colour
properties don't reach it — the shell's palette does.) */
[data-module="propresenter-timers"] .tm-switch {
flex: none;
position: relative;
width: 26px;
height: 14px;
border-radius: 7px;
border: 1px solid var(--border);
background: color-mix(in srgb, var(--muted) 25%, transparent);
transition: background 0.15s ease, border-color 0.15s ease;
}
[data-module="propresenter-timers"] .tm-switch::after {
content: "";
position: absolute;
top: 1px;
left: 1px;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--text);
transition: transform 0.15s ease;
}
[data-module="propresenter-timers"] .tm-menu-item.on .tm-switch {
border-color: var(--accent);
background: var(--accent);
}
[data-module="propresenter-timers"] .tm-menu-item.on .tm-switch::after {
transform: translateX(12px);
background: var(--bg);
}
[data-module="propresenter-timers"] .tm-menu-note {
padding: 6px 9px;
color: var(--muted);
font-size: 12px;
white-space: nowrap;
}