Files
Signage/src/ui/styles.css
T

849 lines
19 KiB
CSS

/*
* The Signage management UI's own stylesheet.
*
* Compiled into the declared browser artifact as text and installed by the host into this artifact's
* isolated root at mount (ADR 0040), so these selectors reach nothing outside Signage and nothing
* outside Signage reaches in.
*
* Every value is either a registered Ekklesia token or a private `--signage-*` alias derived from
* one. Signage declares no `--ekklesia-*` property: those belong to the platform's global theme, and
* the build rejects a stylesheet that invents one (ADR 0039). Nothing here recolours a provider's
* output or an uploaded image — the Dashboard's theme is the Dashboard's, and a television's content
* belongs to whoever made it.
*/
/*
* The box model for the tree Signage owns.
*
* An isolated root inherits nothing from the page around it, and the platform's stylesheet declares
* no reset of its own — so under the initial `content-box` a `.ek-control`, which is `width: 100%`,
* is laid out wider than the field containing it by its own padding and border, and slides under
* whatever sits beside it. Setting the box model once here fixes that everywhere, rather than
* subtracting padding by hand in each panel.
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
:host {
--signage-gap: var(--ekklesia-spacing-xl);
--signage-gap-tight: var(--ekklesia-spacing-sm);
--signage-panel-radius: var(--ekklesia-radius-lg);
--signage-thumbnail-width: 220px;
}
.signage-desktop {
display: none;
}
.signage-narrow-notice {
background: var(--ekklesia-background-surface);
border: var(--ekklesia-border-width) solid var(--ekklesia-border-base);
border-radius: var(--signage-panel-radius);
color: var(--ekklesia-text-primary);
display: grid;
font-family: var(--ekklesia-typography-family-sans);
gap: var(--signage-gap-tight);
padding: var(--ekklesia-spacing-xl);
}
.signage-narrow-notice__title {
font-size: var(--ekklesia-typography-size-heading-3);
font-weight: var(--ekklesia-typography-weight-bold);
line-height: var(--ekklesia-typography-line-height-tight);
margin: 0;
}
.signage-narrow-notice__body {
color: var(--ekklesia-text-secondary);
line-height: var(--ekklesia-typography-line-height-normal);
margin: 0;
}
@media (min-width: 1024px) {
.signage-narrow-notice {
display: none;
}
.signage-desktop {
display: grid;
gap: var(--ekklesia-spacing-xl);
}
}
.signage-workspace {
display: grid;
min-width: 0;
}
.signage-workspace > .ek-tabs__list {
padding-inline: var(--ekklesia-spacing-xs);
}
.signage-columns {
display: grid;
gap: var(--signage-gap);
}
.signage-inline-form {
align-items: end;
display: flex;
flex-wrap: wrap;
gap: var(--signage-gap-tight);
}
/*
* A labelled control beside the button that submits it. The field takes the width its control asks
* for and is allowed to shrink below it, so a narrow header squeezes the control rather than sliding
* the button on top of it.
*/
.signage-inline-form > .ek-field {
min-width: 0;
position: relative;
}
.signage-inline-form .ek-control {
min-width: 0;
}
/*
* On one line, a field's hint hangs below the row rather than making that one field taller. The row
* is bottom-aligned so the controls share a line with the button, and a hint left in flow would push
* its own control above the others.
*/
.signage-inline-form > .ek-field > .ek-field__hint {
inset-block-start: 100%;
inset-inline-start: 0;
padding-block-start: var(--ekklesia-spacing-2xs);
position: absolute;
}
.signage-inline-form:has(.ek-field__hint) {
padding-block-end: var(--ekklesia-spacing-lg);
}
.signage-failure {
color: var(--ekklesia-text-danger);
font-size: var(--ekklesia-typography-size-sm);
margin: var(--signage-gap-tight) 0 0;
}
/*
* A list row that carries a name beside its metadata. The platform's list item is a plain block, so
* without this the two run together into one unreadable word — and the row's own children already
* assume a flex line.
*/
.signage-row {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: var(--ekklesia-spacing-sm);
}
.signage-row__name {
flex: 1 1 auto;
}
/* ---------------------------------------------------------------------------------------------
* Images.
* ------------------------------------------------------------------------------------------- */
.signage-upload-trigger {
cursor: pointer;
display: inline-flex;
}
.signage-upload-input {
height: 1px;
opacity: 0;
position: absolute;
width: 1px;
}
.signage-uploads {
display: grid;
gap: var(--signage-gap-tight);
margin-bottom: var(--signage-gap);
}
.signage-uploads__message {
color: var(--ekklesia-text-secondary);
font-size: var(--ekklesia-typography-size-sm);
}
.signage-image-grid {
display: grid;
gap: var(--signage-gap);
grid-template-columns: repeat(
auto-fill,
minmax(var(--signage-thumbnail-width), 1fr)
);
list-style: none;
margin: 0;
padding: 0;
}
.signage-image {
/* Rows sized to their content, with the slack from a taller neighbour left at the bottom rather
than spread between the thumbnail and the name. */
align-content: start;
background: var(--ekklesia-background-surface);
border: var(--ekklesia-border-width) solid var(--ekklesia-border-divider);
border-radius: var(--ekklesia-radius-md);
display: grid;
gap: var(--ekklesia-spacing-sm);
overflow: hidden;
padding: var(--ekklesia-spacing-md);
}
/* One box, whatever shape the image is: a library of mixed aspect ratios still reads as a grid, and
a tall image is letterboxed here rather than reflowing the card it sits in. */
.signage-image__thumbnail {
aspect-ratio: 16 / 9;
background: var(--ekklesia-background-inset);
border-radius: var(--ekklesia-radius-sm);
display: block;
height: auto;
max-width: 100%;
object-fit: contain;
width: 100%;
}
.signage-image__missing {
align-items: center;
aspect-ratio: 16 / 9;
background: var(--ekklesia-background-inset);
border-radius: var(--ekklesia-radius-sm);
color: var(--ekklesia-text-muted);
display: flex;
font-size: var(--ekklesia-typography-size-sm);
justify-content: center;
}
.signage-image__meta {
display: grid;
gap: var(--ekklesia-spacing-2xs);
}
/* A badge is sized by what it says, not by the column it lands in. */
.signage-image__expiry {
justify-self: start;
}
.signage-image__name {
font-weight: var(--ekklesia-typography-weight-medium);
margin: 0;
overflow-wrap: anywhere;
}
.signage-image__detail {
color: var(--ekklesia-text-secondary);
font-size: var(--ekklesia-typography-size-sm);
margin: 0;
}
.signage-image__actions {
display: flex;
flex-wrap: wrap;
gap: var(--ekklesia-spacing-2xs);
}
/* ---------------------------------------------------------------------------------------------
* Timelines.
* ------------------------------------------------------------------------------------------- */
.signage-editor {
display: grid;
gap: var(--signage-gap);
}
.signage-editor__title {
font-size: var(--ekklesia-typography-size-heading-3);
font-weight: var(--ekklesia-typography-weight-bold);
margin: 0;
}
.signage-editor__subtitle {
color: var(--ekklesia-text-secondary);
font-size: var(--ekklesia-typography-size-sm);
font-weight: var(--ekklesia-typography-weight-medium);
margin: 0;
text-transform: uppercase;
}
.signage-editor__add,
.signage-editor__sources,
.signage-editor__footer {
display: flex;
flex-wrap: wrap;
gap: var(--signage-gap-tight);
}
.signage-editor__sources {
display: grid;
gap: var(--ekklesia-spacing-xs);
}
.signage-editor__source {
align-items: center;
display: flex;
gap: var(--signage-gap-tight);
}
.signage-editor__order {
display: flex;
gap: var(--ekklesia-spacing-2xs);
}
.signage-timeline-name {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: var(--ekklesia-spacing-sm);
}
.signage-timeline-select {
color: var(--ekklesia-text-primary);
font-weight: var(--ekklesia-typography-weight-bold);
padding-inline: var(--ekklesia-spacing-sm);
}
/* ---------------------------------------------------------------------------------------------
* Playback.
* ------------------------------------------------------------------------------------------- */
.signage-playback {
display: grid;
gap: var(--signage-gap);
}
.signage-playback__status,
.signage-playback__controls,
.signage-playback__activate {
align-items: end;
display: flex;
flex-wrap: wrap;
gap: var(--signage-gap-tight);
}
.signage-playback__current {
column-gap: var(--signage-gap);
display: grid;
grid-template-columns: max-content 1fr;
margin: 0;
row-gap: var(--ekklesia-spacing-2xs);
}
.signage-playback__now {
background: var(--ekklesia-background-inset);
border-radius: var(--ekklesia-radius-md);
display: grid;
gap: var(--ekklesia-spacing-sm);
padding: var(--ekklesia-spacing-md);
}
.signage-playback__now h3 {
font-size: var(--ekklesia-typography-size-lg);
margin: 0;
}
.signage-playback__current dt {
color: var(--ekklesia-text-secondary);
font-size: var(--ekklesia-typography-size-sm);
}
.signage-playback__current dd {
font-variant-numeric: tabular-nums;
margin: 0;
}
.signage-playback__history-meta {
color: var(--ekklesia-text-secondary);
font-size: var(--ekklesia-typography-size-sm);
}
.signage-playback__history {
display: grid;
gap: var(--ekklesia-spacing-sm);
}
.signage-playback__history .ek-list {
max-height: 14rem;
overflow: auto;
}
@media (min-width: 1280px) {
.signage-playback-layout {
align-items: start;
grid-template-columns: minmax(0, 1fr) minmax(28rem, 1fr);
}
}
/* ---------------------------------------------------------------------------------------------
* Displays and providers.
* ------------------------------------------------------------------------------------------- */
.signage-groups {
display: grid;
gap: var(--signage-gap);
}
.signage-pairing-steps {
display: grid;
gap: var(--signage-gap-tight);
grid-template-columns: repeat(3, minmax(0, 1fr));
list-style: none;
margin: 0;
padding: 0;
}
.signage-pairing-actions {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: var(--signage-gap-tight);
}
.signage-pairing-actions a {
text-decoration: none;
}
.signage-copy-status {
color: var(--ekklesia-text-secondary);
display: block;
font-size: var(--ekklesia-typography-size-sm);
min-height: 1.25em;
margin-block-start: var(--ekklesia-spacing-xs);
}
.signage-pairing-steps li {
align-items: center;
background: var(--ekklesia-accent-subtle);
border-radius: var(--ekklesia-radius-md);
display: flex;
gap: var(--signage-gap-tight);
min-width: 0;
padding: var(--ekklesia-spacing-md);
}
.signage-pairing-steps__number {
align-items: center;
background: var(--ekklesia-accent-base);
border-radius: var(--ekklesia-radius-pill);
color: var(--ekklesia-accent-foreground);
display: inline-flex;
flex: 0 0 var(--ekklesia-control-height-sm);
font-weight: var(--ekklesia-typography-weight-bold);
height: var(--ekklesia-control-height-sm);
justify-content: center;
}
.signage-display-empty {
align-items: baseline;
color: var(--ekklesia-text-secondary);
display: flex;
flex-wrap: wrap;
gap: var(--ekklesia-spacing-xs) var(--signage-gap-tight);
margin-block-start: var(--signage-gap-tight);
}
.signage-display-empty strong {
color: var(--ekklesia-text-primary);
}
.signage-new-group > .ek-field {
min-width: 16rem;
}
.signage-group {
background: var(--ekklesia-background-inset);
border: var(--ekklesia-border-width) solid var(--ekklesia-border-divider);
border-radius: var(--signage-panel-radius);
display: grid;
gap: var(--signage-gap);
padding: var(--ekklesia-spacing-lg);
}
.signage-group__header {
align-items: center;
display: flex;
gap: var(--signage-gap-tight);
}
.signage-group__header > .ek-button {
margin-inline-start: auto;
}
.signage-group__identity {
display: grid;
gap: var(--ekklesia-spacing-2xs);
}
.signage-group__identity h3,
.signage-audiences__heading h4,
.signage-channel__header h5,
.signage-channel-form__heading h5 {
margin: 0;
}
.signage-hierarchy-label {
color: var(--ekklesia-text-muted);
font-size: var(--ekklesia-typography-size-xs);
font-weight: var(--ekklesia-typography-weight-medium);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.signage-confirm {
align-items: center;
background: var(--ekklesia-status-danger-background);
border-radius: var(--ekklesia-radius-md);
color: var(--ekklesia-text-danger);
display: flex;
flex-wrap: wrap;
gap: var(--signage-gap-tight);
padding: var(--ekklesia-spacing-sm);
}
.signage-confirm p {
flex: 1 1 30rem;
margin: 0;
}
.signage-audiences {
border-block-start: var(--ekklesia-border-width) solid
var(--ekklesia-border-divider);
display: grid;
gap: var(--signage-gap);
padding-block-start: var(--signage-gap);
}
.signage-audiences__heading {
align-items: end;
display: flex;
gap: var(--signage-gap);
justify-content: space-between;
}
.signage-audiences__heading > div {
display: grid;
gap: var(--ekklesia-spacing-2xs);
}
.signage-audiences__heading > span {
color: var(--ekklesia-text-secondary);
font-size: var(--ekklesia-typography-size-sm);
}
.signage-channel-list {
display: grid;
gap: var(--signage-gap-tight);
}
.signage-channel {
background: var(--ekklesia-background-surface);
border: var(--ekklesia-border-width) solid var(--ekklesia-border-divider);
border-inline-start: var(--ekklesia-spacing-2xs) solid
var(--ekklesia-accent-base);
border-radius: var(--ekklesia-radius-md);
display: grid;
gap: var(--signage-gap-tight);
margin-inline-start: var(--ekklesia-spacing-md);
padding: var(--ekklesia-spacing-md);
}
.signage-channel__header {
align-items: start;
display: flex;
gap: var(--signage-gap-tight);
justify-content: space-between;
}
.signage-channel__header > div {
display: grid;
gap: var(--ekklesia-spacing-xs);
}
.signage-channel__meta {
align-items: center;
color: var(--ekklesia-text-secondary);
display: flex;
flex-wrap: wrap;
font-size: var(--ekklesia-typography-size-sm);
gap: var(--ekklesia-spacing-xs);
}
.signage-channel__meta code,
.signage-pairing-steps code {
background: var(--ekklesia-background-inset);
border-radius: var(--ekklesia-radius-sm);
color: var(--ekklesia-text-primary);
padding: var(--ekklesia-spacing-2xs) var(--ekklesia-spacing-xs);
}
.signage-channel__displays {
border-block-start: var(--ekklesia-border-width) solid
var(--ekklesia-border-divider);
display: grid;
gap: var(--signage-gap-tight);
padding-block-start: var(--signage-gap-tight);
}
.signage-channel__empty {
color: var(--ekklesia-text-muted);
font-size: var(--ekklesia-typography-size-sm);
margin: 0;
}
.signage-display-row {
align-items: end;
background: var(--ekklesia-background-canvas);
border-radius: var(--ekklesia-radius-md);
display: grid;
gap: var(--signage-gap-tight);
grid-template-columns: minmax(14rem, 1.2fr) minmax(14rem, 1fr) minmax(
14rem,
1.2fr
);
padding: var(--ekklesia-spacing-sm);
}
.signage-display-row__identity,
.signage-display-row__health {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: var(--ekklesia-spacing-xs);
}
.signage-display-row__identity .ek-control,
.signage-display-row__assignment .ek-field {
width: 100%;
}
.signage-display-row__health {
color: var(--ekklesia-text-secondary);
font-size: var(--ekklesia-typography-size-sm);
}
.signage-display-row__error {
color: var(--ekklesia-text-danger);
}
.signage-channel-form,
.signage-unassigned {
background: var(--ekklesia-background-surface);
border: var(--ekklesia-border-width) solid var(--ekklesia-border-divider);
border-radius: var(--ekklesia-radius-md);
display: grid;
gap: var(--signage-gap-tight);
margin-inline-start: var(--ekklesia-spacing-md);
padding: var(--ekklesia-spacing-md);
}
.signage-channel-form {
align-items: start;
grid-template-columns: minmax(16rem, 2fr) minmax(14rem, 1fr);
}
.signage-channel-form__heading,
.signage-channel-form > .ek-button {
grid-column: 1 / -1;
}
.signage-channel-form__heading {
display: grid;
gap: var(--ekklesia-spacing-2xs);
}
.signage-channel-form__heading span {
color: var(--ekklesia-text-secondary);
font-size: var(--ekklesia-typography-size-sm);
}
.signage-channel-form > .ek-button {
justify-self: start;
}
@media (max-width: 1200px) {
.signage-pairing-steps,
.signage-channel-form,
.signage-display-row {
grid-template-columns: 1fr;
}
}
.signage-provider__id {
font-weight: var(--ekklesia-typography-weight-medium);
}
.signage-provider__version {
color: var(--ekklesia-text-muted);
font-size: var(--ekklesia-typography-size-sm);
/* Far enough that the version reads as a second word, not the tail of the identifier. */
margin-left: var(--ekklesia-spacing-sm);
}
/* ---------------------------------------------------------------------------------------------
* Live preview.
*
* The frame is a 16:9 box with a black ground, because that is what a television is. Inside it the
* display's own scene layers render exactly as they do on a wall — this panel supplies a size and
* nothing else.
* ------------------------------------------------------------------------------------------- */
.signage-preview {
display: grid;
gap: var(--signage-gap-tight);
}
.signage-preview__frame {
aspect-ratio: 16 / 9;
background: #000000;
border: var(--ekklesia-border-width) solid var(--ekklesia-border-strong);
border-radius: var(--ekklesia-radius-md);
overflow: hidden;
position: relative;
width: 100%;
}
.signage-preview .signage-stage {
height: 100%;
inset: 0;
min-height: 0;
position: absolute;
width: 100%;
}
.signage-preview .signage-layer {
height: 100%;
inset: 0;
opacity: 0;
position: absolute;
transition-duration: 0ms;
transition-property: opacity;
transition-timing-function: linear;
width: 100%;
}
.signage-preview .signage-layer__background {
background: #000000;
inset: 0;
position: absolute;
}
.signage-preview .signage-layer__background-image {
display: none;
height: 100%;
inset: 0;
object-fit: cover;
position: absolute;
transform: scale(1.08);
width: 100%;
}
.signage-preview .signage-layer__darken {
background: #000000;
inset: 0;
opacity: 0;
position: absolute;
}
.signage-preview .signage-layer__foreground {
height: 100%;
inset: 0;
object-fit: contain;
position: absolute;
width: 100%;
}
.signage-preview .signage-layer__renderer {
inset: 0;
position: absolute;
}
.signage-preview .signage-blackout {
background: #000000;
inset: 0;
opacity: 0;
pointer-events: none;
position: absolute;
transition: opacity 200ms linear;
z-index: 20;
}
.signage-preview [data-blackout="true"] > .signage-blackout {
opacity: 1;
}
.signage-preview .signage-slate-host {
inset: 0;
opacity: 0;
pointer-events: none;
position: absolute;
z-index: 10;
}
.signage-preview [data-slate="true"] > .signage-slate-host {
opacity: 1;
}
.signage-preview .signage-slate {
align-items: stretch;
display: flex;
flex-direction: column;
height: 100%;
inset: 0;
position: absolute;
width: 100%;
}
.signage-preview .signage-slate__bars {
display: grid;
flex: 1 1 auto;
grid-template-columns: repeat(6, 1fr);
}
.signage-preview .signage-slate__bar {
display: block;
height: 100%;
}
.signage-preview .signage-slate__bar--one {
background: #6f6f6f;
}
.signage-preview .signage-slate__bar--two {
background: #6f6f2f;
}
.signage-preview .signage-slate__bar--three {
background: #2f6f6f;
}
.signage-preview .signage-slate__bar--four {
background: #2f6f2f;
}
.signage-preview .signage-slate__bar--five {
background: #6f2f6f;
}
.signage-preview .signage-slate__bar--six {
background: #6f2f2f;
}
.signage-preview .signage-slate__message {
background: #000000;
color: #ffffff;
flex: 0 0 auto;
font-size: 2vh;
letter-spacing: 0.08em;
margin: 0;
padding: 1.5vh 0;
text-align: center;
text-transform: uppercase;
}