Files
Waters ChurchandClaude Opus 5.5 41fd76817c AbleSet 1.0.0: follow AbleSet's setlist over OSC
A new module that subscribes to AbleSet's OSC API (UDP 39051) and streams
what it is playing to ten tiles: Now Playing (song, tempo, meter, duration,
time left, section, progress bar, next song, songs left), Song Sections
(a sliding strip, or a lottery wheel from the title bar), Setlist,
Transport, Tempo, Song Time, Set Time, Section, Next Song and Status, each
coloured by its state. The song and set countdowns are offered to the
Timers module.

A heartbeat watchdog resubscribes when AbleSet goes quiet (a restarted
AbleSet forgets its subscribers). Time left falls back to the song length
and playhead when AbleSet stops sending it (Live disconnected).

tools/ableset-mock.js plays a setlist over the same API for testing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 14:24:19 -04:00

417 lines
15 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* AbleSet tiles. Every size hangs off the tile itself (container units), so
a tile reads the same at 2×1 on a laptop as at 12×6 on a wall. Colours are
the shell's variables; a tone (ok / warn / alert / muted) picks among them,
and AbleSet's own song or section colour arrives as --song when a tile is
set to use it. */
[data-module="ableset"] .as-root {
--tone: var(--text);
--live: var(--accent);
position: absolute;
inset: 0;
container-type: size;
overflow: hidden;
user-select: none;
-webkit-user-select: none;
color: var(--text);
font-variant-numeric: tabular-nums;
}
[data-module="ableset"] .as-root.has-song-color { --live: var(--song); }
[data-module="ableset"] .as-root[data-tone="ok"] { --tone: var(--accent); }
[data-module="ableset"] .as-root[data-tone="warn"] { --tone: var(--warn); }
[data-module="ableset"] .as-root[data-tone="alert"] { --tone: var(--danger); }
[data-module="ableset"] .as-root[data-tone="muted"] { --tone: var(--muted); }
[data-module="ableset"] .as-body {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: center;
padding: clamp(4px, 4cqmin, 28px);
gap: clamp(2px, 2.5cqh, 18px);
min-width: 0;
}
[data-module="ableset"] [hidden] { display: none !important; }
[data-module="ableset"] .as-offline {
position: absolute;
top: clamp(3px, 3cqmin, 14px);
right: clamp(4px, 4cqmin, 18px);
z-index: 2;
font-size: clamp(9px, 6cqmin, 15px);
font-weight: 700;
color: var(--danger);
text-transform: uppercase;
letter-spacing: 0.04em;
}
[data-module="ableset"] .as-root.is-offline .as-body { opacity: 0.35; filter: grayscale(0.7); }
[data-module="ableset"] [data-fit] {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
/* ── the song bar: played sections · this section · the playhead ── */
[data-module="ableset"] .as-bar {
--done: 0%;
--sect-l: 0%;
--sect-w: 0%;
--played: 0%;
position: relative;
flex-shrink: 0;
height: clamp(4px, 5.5cqh, 26px);
border-radius: 999px;
overflow: hidden;
background: color-mix(in srgb, var(--live) 14%, var(--panel));
}
[data-module="ableset"] .as-bar-done {
position: absolute;
inset: 0 auto 0 0;
width: var(--done);
background: color-mix(in srgb, var(--live) 32%, var(--panel));
}
[data-module="ableset"] .as-bar-sect {
position: absolute;
top: 0;
bottom: 0;
left: var(--sect-l);
width: var(--sect-w);
background: color-mix(in srgb, var(--live) 55%, var(--panel));
overflow: hidden;
}
[data-module="ableset"] .as-bar-played {
position: absolute;
inset: 0 auto 0 0;
width: var(--played);
background: linear-gradient(90deg,
color-mix(in srgb, var(--live) 70%, var(--panel)) 0%,
color-mix(in srgb, var(--live) 70%, var(--panel)) calc(100% - 3.5cqw),
color-mix(in srgb, var(--live) 70%, var(--text)) 100%);
}
/* ── NOW PLAYING ─────────────────────────────────────────────────── */
[data-module="ableset"] .as-now .as-body {
align-items: center;
padding-inline: clamp(6px, 7cqw, 90px);
gap: clamp(3px, 4cqh, 30px);
}
[data-module="ableset"] .as-now .as-title-box,
[data-module="ableset"] .as-now .as-next-box { width: 100%; display: flex; justify-content: center; min-width: 0; }
[data-module="ableset"] .as-now .as-title {
font-size: clamp(14px, 20cqh, 170px);
font-weight: 800;
line-height: 1.05;
letter-spacing: -0.01em;
color: var(--text);
transition: color 0.3s;
}
[data-module="ableset"] .as-now.is-playing .as-title { color: var(--live); }
[data-module="ableset"] .as-now .as-bar { width: 100%; }
[data-module="ableset"] .as-badges {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: clamp(3px, 1.8cqh, 16px);
max-width: 100%;
}
[data-module="ableset"] .as-badge {
font-size: clamp(9px, 7cqh, 44px);
font-weight: 700;
line-height: 1;
padding: 0.28em 0.5em;
border-radius: 0.22em;
background: color-mix(in srgb, var(--text) 12%, var(--panel));
color: color-mix(in srgb, var(--text) 80%, var(--accent));
white-space: nowrap;
}
[data-module="ableset"] .as-badge.b-section {
color: var(--text);
text-transform: uppercase;
background: color-mix(in srgb, var(--text) 7%, var(--panel));
}
[data-module="ableset"] .as-badge.b-clock[data-tone="warn"] { color: var(--accent-text); background: var(--warn); }
[data-module="ableset"] .as-badge.b-clock[data-tone="alert"] { color: var(--accent-text); background: var(--danger); }
[data-module="ableset"] .as-next {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
min-width: 0;
gap: clamp(1px, 0.6cqh, 6px);
}
[data-module="ableset"] .as-next-label {
font-size: clamp(8px, 5cqh, 30px);
font-weight: 700;
color: color-mix(in srgb, var(--live) 55%, var(--muted));
}
[data-module="ableset"] .as-next.is-queued .as-next-label { color: var(--warn); }
[data-module="ableset"] .as-next-title {
font-size: clamp(11px, 12cqh, 90px);
font-weight: 500;
line-height: 1.1;
color: color-mix(in srgb, var(--live) 45%, var(--muted));
}
[data-module="ableset"] .as-left {
font-size: clamp(8px, 6cqh, 36px);
color: color-mix(in srgb, var(--live) 40%, var(--muted));
}
/* ── SONG SECTIONS ──────────────────────────────────────────────── */
[data-module="ableset"] .as-sections .as-body { padding: clamp(3px, 3cqmin, 16px) 0; gap: clamp(2px, 2cqh, 10px); }
[data-module="ableset"] .as-sections .as-song-name {
flex-shrink: 0;
text-align: center;
padding: 0 clamp(4px, 3cqw, 20px);
font-size: clamp(9px, 13cqh, 28px);
font-weight: 700;
color: var(--muted);
}
[data-module="ableset"] .as-viewport {
position: relative;
flex: 1;
min-height: 0;
overflow: hidden;
-webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
[data-module="ableset"] .as-track {
position: absolute;
top: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
gap: clamp(4px, 2cqw, 18px);
transition: transform 0.65s cubic-bezier(0.22, 0.9, 0.3, 1);
will-change: transform;
}
[data-module="ableset"] .as-track.no-anim,
[data-module="ableset"] .as-track.no-anim .as-pill { transition: none !important; }
[data-module="ableset"] .as-pill {
--p: 0%;
--sect: var(--accent);
position: relative;
flex-shrink: 0;
display: flex;
align-items: center;
height: min(78%, 22cqw);
padding: 0 0.8em;
border-radius: 0.35em;
font-size: clamp(9px, 26cqh, 64px);
font-weight: 700;
line-height: 1;
white-space: nowrap;
overflow: hidden;
color: var(--muted);
background: color-mix(in srgb, var(--text) 8%, var(--panel));
border: 2px solid transparent;
transition: background 0.4s, color 0.4s, border-color 0.4s, opacity 0.5s, transform 0.65s cubic-bezier(0.22, 0.9, 0.3, 1);
}
[data-module="ableset"] .as-pill.has-color { background: color-mix(in srgb, var(--sect) 22%, var(--panel)); }
[data-module="ableset"] .as-pill.is-past { opacity: 0.45; }
[data-module="ableset"] .as-pill.is-next { border-color: color-mix(in srgb, var(--sect) 60%, transparent); color: var(--text); }
[data-module="ableset"] .as-pill.is-current {
color: var(--accent-text);
background: color-mix(in srgb, var(--sect) 55%, var(--panel));
}
[data-module="ableset"] .as-pill-fill {
position: absolute;
inset: 0 auto 0 0;
width: 0;
background: var(--sect);
}
[data-module="ableset"] .as-pill.is-current .as-pill-fill { width: var(--p); }
[data-module="ableset"] .as-pill-text { position: relative; }
[data-module="ableset"] .as-pill.is-empty { color: var(--muted); }
/* the lottery wheel: faces on a drum turning towards the viewer */
[data-module="ableset"] .is-wheel .as-viewport {
perspective: 900px;
-webkit-mask-image: linear-gradient(180deg, transparent, #000 22%, #000 78%, transparent);
mask-image: linear-gradient(180deg, transparent, #000 22%, #000 78%, transparent);
}
[data-module="ableset"] .is-wheel .as-track {
--face-h: 30px;
inset: 50% 0 auto 0;
display: block;
height: 0;
transform-style: preserve-3d;
transition: none;
}
[data-module="ableset"] .is-wheel .as-pill {
position: absolute;
left: 50%;
top: 0;
translate: -50% 0;
height: var(--face-h);
justify-content: center;
min-width: 55%;
max-width: 94%;
font-size: clamp(9px, calc(var(--face-h) * 0.55), 72px);
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
/* ── SETLIST ─────────────────────────────────────────────────────── */
[data-module="ableset"] .as-setlist .as-body { justify-content: flex-start; gap: 6px; padding: 8px; }
[data-module="ableset"] .as-set-head {
flex-shrink: 0;
font-size: 0.8rem;
font-weight: 700;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.05em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
[data-module="ableset"] .as-list { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 3px; scrollbar-width: thin; }
[data-module="ableset"] .as-row {
--p: 0%;
--song: var(--accent);
position: relative;
display: flex;
align-items: center;
gap: 0.5em;
padding: 0.35em 0.55em;
border-radius: 6px;
font-size: clamp(11px, 5.5cqw, 19px);
background: color-mix(in srgb, var(--text) 5%, transparent);
overflow: hidden;
flex-shrink: 0;
}
[data-module="ableset"] .as-row-fill { position: absolute; inset: 0 auto 0 0; width: 0; background: color-mix(in srgb, var(--accent) 22%, transparent); }
[data-module="ableset"] .as-row.is-current .as-row-fill { width: var(--p); }
[data-module="ableset"] .as-row-name { position: relative; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
[data-module="ableset"] .as-row-dur { position: relative; color: var(--muted); font-size: 0.85em; }
[data-module="ableset"] .as-swatch { position: relative; width: 0.55em; height: 0.55em; border-radius: 50%; background: var(--song); flex-shrink: 0; }
[data-module="ableset"] .as-row.is-past { opacity: 0.4; }
[data-module="ableset"] .as-row.is-current { font-weight: 700; color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); box-shadow: inset 3px 0 0 var(--accent); }
[data-module="ableset"] .as-row.is-next { box-shadow: inset 3px 0 0 color-mix(in srgb, var(--text) 40%, transparent); }
[data-module="ableset"] .as-row.is-queued { box-shadow: inset 3px 0 0 var(--warn); color: var(--warn); }
[data-module="ableset"] .as-set-foot { flex-shrink: 0; font-size: 0.85rem; color: var(--muted); text-align: right; }
[data-module="ableset"] .as-empty { color: var(--muted); padding: 1em; text-align: center; }
/* ── single readouts: transport, tempo, times, section, next ────── */
[data-module="ableset"] .as-big-box { display: flex; justify-content: center; min-width: 0; width: 100%; }
[data-module="ableset"] .as-big {
font-size: clamp(14px, 38cqh, 220px);
font-weight: 800;
line-height: 1;
color: var(--tone);
text-align: center;
transition: color 0.3s;
}
[data-module="ableset"] .as-big small { font-size: 0.35em; font-weight: 700; color: var(--muted); }
[data-module="ableset"] .as-caption {
text-align: center;
font-size: clamp(8px, 12cqh, 30px);
font-weight: 700;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
[data-module="ableset"] .as-sub {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.25em 1em;
font-size: clamp(8px, 12cqh, 34px);
color: var(--muted);
text-align: center;
white-space: nowrap;
}
[data-module="ableset"] .as-sub:empty { display: none; }
/* A tone washes the tile's background, so a wall of them reads at a glance. */
[data-module="ableset"] .as-transport,
[data-module="ableset"] .as-song-time,
[data-module="ableset"] .as-section,
[data-module="ableset"] .as-status,
[data-module="ableset"] .as-next-song {
background: color-mix(in srgb, var(--tone) 9%, transparent);
transition: background 0.3s;
}
[data-module="ableset"] .as-root[data-tone="muted"],
[data-module="ableset"] .as-root[data-tone=""] { background: none; }
[data-module="ableset"] .as-transport .as-big { text-transform: uppercase; font-size: clamp(12px, 34cqh, 180px); }
[data-module="ableset"] .as-transport .as-loop { color: var(--warn); font-weight: 700; }
[data-module="ableset"] .as-tempo .as-big { color: var(--text); }
[data-module="ableset"] .as-beats { display: flex; justify-content: center; gap: clamp(3px, 3cqw, 14px); flex-shrink: 0; }
[data-module="ableset"] .as-beats i {
width: clamp(6px, 11cqh, 30px);
aspect-ratio: 1;
border-radius: 50%;
background: color-mix(in srgb, var(--text) 14%, var(--panel));
transition: background 0.08s;
}
[data-module="ableset"] .as-beats i.is-lit { background: var(--text); }
[data-module="ableset"] .as-beats i.is-down.is-lit { background: var(--accent); }
[data-module="ableset"] .as-song-time .as-bar { height: clamp(3px, 5cqh, 14px); }
[data-module="ableset"] .as-section .as-big { text-transform: uppercase; }
[data-module="ableset"] .as-section.has-song-color .as-big { color: var(--song); }
[data-module="ableset"] .as-section .as-count { color: var(--tone); font-weight: 700; }
[data-module="ableset"] .as-sect-bar {
--p: 0%;
height: clamp(3px, 4cqh, 12px);
border-radius: 999px;
background: color-mix(in srgb, var(--text) 12%, var(--panel));
overflow: hidden;
flex-shrink: 0;
}
[data-module="ableset"] .as-sect-bar > div { height: 100%; width: var(--p); background: var(--tone); }
[data-module="ableset"] .as-next-song .as-big { color: var(--text); font-size: clamp(12px, 30cqh, 150px); }
[data-module="ableset"] .as-queued {
align-self: center;
font-size: clamp(8px, 11cqh, 28px);
font-weight: 700;
padding: 0.2em 0.6em;
border-radius: 0.3em;
color: var(--accent-text);
background: var(--warn);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
/* ── STATUS ──────────────────────────────────────────────────────── */
[data-module="ableset"] .as-status .as-body { justify-content: flex-start; padding: 8px 10px; }
[data-module="ableset"] .as-rows { display: flex; flex-direction: column; gap: 4px; }
[data-module="ableset"] .as-srow {
--dot: var(--muted);
display: flex;
align-items: center;
gap: 0.55em;
font-size: clamp(11px, 4.5cqw, 18px);
padding: 0.3em 0.5em;
border-radius: 6px;
background: color-mix(in srgb, var(--dot) 10%, transparent);
}
[data-module="ableset"] .as-srow[data-tone="ok"] { --dot: var(--accent); }
[data-module="ableset"] .as-srow[data-tone="warn"] { --dot: var(--warn); }
[data-module="ableset"] .as-srow[data-tone="alert"] { --dot: var(--danger); }
[data-module="ableset"] .as-srow[data-tone=""] { --dot: var(--text); background: color-mix(in srgb, var(--text) 5%, transparent); }
[data-module="ableset"] .as-srow i { width: 0.6em; height: 0.6em; border-radius: 50%; background: var(--dot); flex-shrink: 0; }
[data-module="ableset"] .as-sk { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
[data-module="ableset"] .as-sv { font-weight: 700; color: var(--dot); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 60%; }
[data-module="ableset"] .as-srow[data-tone=""] .as-sv { color: var(--text); }