/* * 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; }