Files

1207 lines
21 KiB
CSS

* {
box-sizing: border-box;
}
html,
body {
margin: 0;
height: 100%;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
background: #0f0f12;
color: #e8e8ec;
user-select: none;
}
body {
display: flex;
flex-direction: column;
overflow: hidden;
}
.top-edge-hit {
display: none;
}
.toolbar {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
background: #1a1a20;
border-bottom: 1px solid #2a2a32;
flex-shrink: 0;
overflow: hidden;
max-height: 72px;
opacity: 1;
transition: max-height 0.22s ease, padding 0.22s ease, opacity 0.22s ease, transform 0.22s ease, border-color 0.22s ease;
}
.toolbar-spacer {
flex: 1;
}
.app-version {
flex-shrink: 0;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
color: #6f6f7d;
white-space: nowrap;
user-select: text;
}
.layout-controls {
display: flex;
align-items: center;
gap: 10px;
}
.layout-field {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #a0a0ae;
}
.layout-field input {
width: 64px;
padding: 7px 8px;
border-radius: 8px;
border: 1px solid #3a3a44;
background: #25252e;
color: #e8e8ec;
font-size: 14px;
}
.layout-field input:focus {
outline: none;
border-color: #4a6fa5;
}
.btn {
padding: 8px 14px;
border-radius: 8px;
border: 1px solid #3a3a44;
background: #25252e;
color: #e8e8ec;
font-size: 14px;
cursor: pointer;
touch-action: manipulation;
}
.btn:hover {
background: #32323c;
}
.btn.primary {
background: #3d5a80;
border-color: #4a6fa5;
}
.btn.primary:hover {
background: #4a6fa5;
}
.btn.btn-small {
padding: 6px 10px;
font-size: 12px;
}
.app-main {
flex: 1;
min-height: 0;
display: flex;
align-items: stretch;
}
.stage {
flex: 1;
min-width: 0;
position: relative;
display: flex;
align-items: stretch;
justify-content: stretch;
padding: 12px 14px 12px 16px;
overflow: hidden;
}
.stage.drag-over {
outline: 2px dashed #4a6fa5;
outline-offset: -6px;
background: #15151a;
}
.row {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
grid-template-rows: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.stage-slot {
min-width: 0;
min-height: 0;
position: relative;
border-radius: 12px;
overflow: hidden;
background: #15151b;
border: 2px solid #282832;
}
.stage-slot.empty-slot {
border-style: dashed;
border-color: #343442;
background: rgba(30, 30, 38, 0.65);
}
.stage-slot.empty-slot::before {
content: attr(data-slot-label);
position: absolute;
top: 8px;
left: 10px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #646474;
}
.slot-placeholder {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 12px;
color: #555563;
font-size: 14px;
}
.slot-visual {
position: relative;
flex: 1;
min-height: 0;
overflow: hidden;
}
.library-panel {
width: clamp(320px, 30vw, 420px);
min-width: 320px;
flex-shrink: 0;
display: flex;
flex-direction: column;
/* Show mode collapses this panel to width 0 rather than hiding it; without clipping, the
tab strip and pane contents spill across the stage. */
overflow: hidden;
background: #141418;
border-left: 1px solid #2a2a32;
transition: width 0.22s ease, min-width 0.22s ease, border-color 0.22s ease;
}
/* ---------- panel tabs ---------- */
.library-tabs {
display: flex;
flex-shrink: 0;
gap: 4px;
padding: 8px 8px 0;
border-bottom: 1px solid #2a2a32;
transition: opacity 0.18s ease;
}
.lib-tab {
flex: 1;
min-width: 0;
padding: 9px 6px;
font-family: inherit;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #8a8a99;
background: none;
border: none;
border-bottom: 2px solid transparent;
border-radius: 6px 6px 0 0;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.lib-tab:hover {
color: #c6cada;
background: rgba(255, 255, 255, 0.03);
}
.lib-tab.is-active {
color: #cfe0f7;
border-bottom-color: #4a6fa5;
background: rgba(74, 111, 165, 0.12);
}
/* One pane at a time. display:none rather than opacity, so a hidden pane cannot be
clicked, tabbed into, or measured. */
.library-pane {
display: none;
min-height: 0;
transition: opacity 0.18s ease;
}
.library-pane.is-active {
/* This chain is load-bearing: .library-scroll's flex:1 only expands because every
ancestor up to .library-panel is a column flex box with min-height:0. */
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
}
.pane-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
padding: 10px;
touch-action: pan-y;
}
.pane-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.pane-head-spaced {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid #2a2a32;
}
.pane-title {
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #9a9aa8;
}
.pane-foot {
flex-shrink: 0;
padding: 6px 10px 8px;
border-top: 1px solid #2a2a32;
}
.slot-size-readout {
font-size: 11px;
line-height: 1.4;
color: #7d8492;
user-select: text;
}
.pco-controls {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
gap: 8px;
margin-top: 8px;
}
.pco-select {
min-width: 0;
width: 100%;
padding: 7px 10px;
border-radius: 8px;
border: 1px solid #3a3a44;
background: #25252e;
color: #e8e8ec;
font-size: 13px;
}
.pco-status {
margin-top: 8px;
min-height: 18px;
font-size: 12px;
color: #9ea7bc;
}
.pco-position-builder {
display: none;
grid-template-columns: minmax(0, 1fr) auto;
gap: 8px;
margin-top: 8px;
}
.pco-position-list {
display: none;
flex-direction: column;
gap: 6px;
margin-top: 8px;
max-height: 170px;
overflow-y: auto;
}
.pco-position-empty {
padding: 8px 10px;
border: 1px dashed #353542;
border-radius: 8px;
color: #808092;
font-size: 12px;
}
.pco-position-item {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto auto auto auto auto;
gap: 6px;
align-items: center;
padding: 8px;
border: 1px solid #30303a;
border-radius: 8px;
background: #1a1a20;
}
.pco-position-copy {
min-width: 0;
}
.pco-position-name {
font-size: 12px;
font-weight: 600;
color: #ececf1;
}
.pco-position-team {
margin-top: 2px;
font-size: 11px;
color: #9da5b6;
}
.pco-count-chip {
min-width: 34px;
text-align: center;
font-size: 12px;
color: #d8deea;
}
.library-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
padding: 8px;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
align-content: start;
touch-action: pan-y;
transition: opacity 0.18s ease;
}
.lib-item {
position: relative;
width: 100%;
aspect-ratio: 3 / 4;
border-radius: 10px;
overflow: hidden;
border: 2px solid #33333e;
background: #0a0a0c;
flex-shrink: 0;
touch-action: pan-y;
cursor: grab;
}
.lib-item:active {
cursor: grabbing;
}
.lib-item.dragging-source {
opacity: 0.35;
}
.lib-item.on-lineup {
border-color: #3d5a80;
box-shadow: 0 0 0 1px rgba(61, 90, 128, 0.5);
}
.lib-item img {
width: 100%;
height: 100%;
aspect-ratio: 3 / 4;
object-fit: cover;
object-position: center top;
display: block;
pointer-events: none;
}
.lib-name-overlay {
position: absolute;
top: 8px;
left: 8px;
right: 8px;
z-index: 1;
padding: 7px 8px;
border-radius: 8px;
background: rgba(8, 8, 12, 0.74);
color: #f4f4f6;
font-size: 11px;
font-weight: 700;
line-height: 1.2;
text-align: center;
word-break: break-word;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.65);
pointer-events: none;
}
.lib-delete {
position: absolute;
top: 4px;
right: 4px;
width: 24px;
height: 24px;
border: none;
border-radius: 8px;
background: rgba(0, 0, 0, 0.6);
color: #fff;
font-size: 14px;
line-height: 1;
cursor: pointer;
z-index: 2;
touch-action: manipulation;
padding: 0;
}
.lib-delete:hover {
background: #8b2942;
}
.tile {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
border-radius: 12px;
overflow: hidden;
background: #1a1a22;
position: relative;
touch-action: none;
cursor: grab;
}
.tile:active {
cursor: grabbing;
}
.tile.dragging-source {
opacity: 0.4;
}
.tile .photo-area {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: #0a0a0c;
}
.tile img {
width: 100%;
height: 100%;
object-fit: contain;
object-position: center center;
pointer-events: none;
display: block;
}
.tile .parallax-viewport {
position: absolute;
inset: 0;
z-index: 1;
overflow: hidden;
pointer-events: none;
}
.tile .moving-stage-image {
position: absolute;
inset: -5%;
width: 110%;
height: 110%;
object-fit: cover;
object-position: center center;
will-change: transform;
backface-visibility: hidden;
transform-origin: center center;
image-rendering: -webkit-optimize-contrast;
transform: translate3d(calc(var(--cam-x, 0px) * -0.3), calc(var(--cam-y, 0px) * -0.3), 0) scale(1.01);
}
.tile .static-stage-image {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center center;
}
.tile .name-badge {
position: absolute;
top: 8px;
left: 8px;
max-width: calc(100% - 52px);
padding: 6px 10px;
border-radius: 8px;
background: rgba(8, 8, 12, 0.84);
color: #f4f4f6;
font-size: clamp(11px, 1vw, 15px);
font-weight: 700;
line-height: 1.2;
z-index: 3;
cursor: pointer;
touch-action: manipulation;
word-break: break-word;
}
.empty-name-hotspot {
position: absolute;
top: 8px;
left: 8px;
right: 40px;
min-height: 28px;
border: 0;
border-radius: 8px;
padding: 6px 10px;
background: transparent;
color: #f4f4f6;
font-size: clamp(11px, 1vw, 15px);
font-weight: 700;
line-height: 1.2;
text-align: left;
z-index: 2;
cursor: pointer;
touch-action: manipulation;
}
.empty-name-hotspot:not(:empty) {
background: rgba(8, 8, 12, 0.68);
}
.empty-name-hotspot:focus-visible {
outline: 2px solid #6b9ee0;
outline-offset: 1px;
}
.tile .name-badge:empty::before {
content: "Tap for name";
color: #b2b2c0;
font-weight: 600;
}
.tile .caption {
position: relative;
padding: 10px 8px;
font-size: clamp(11px, 1vw, 15px);
font-weight: 600;
text-align: center;
background: rgba(12, 12, 16, 0.88);
border-top: 1px solid #2e2e38;
min-height: 42px;
display: flex;
align-items: center;
justify-content: center;
word-break: break-word;
touch-action: manipulation;
cursor: pointer;
}
.stage-slot > .caption:empty::before {
content: "Tap for mic / pack";
color: #6b6b79;
font-weight: 500;
}
.tile .remove {
position: absolute;
top: 6px;
right: 6px;
width: 30px;
height: 30px;
border: none;
border-radius: 8px;
background: rgba(0, 0, 0, 0.6);
color: #fff;
font-size: 18px;
line-height: 1;
cursor: pointer;
z-index: 4;
touch-action: manipulation;
padding: 0;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s ease;
}
body:not(.chrome-hidden) .tile .remove {
opacity: 1;
pointer-events: auto;
}
.tile .remove:hover {
background: #8b2942;
}
.stage-slot {
display: flex;
flex-direction: column;
}
.stage-slot > .caption {
flex-shrink: 0;
padding: 10px 8px;
font-size: clamp(11px, 1vw, 15px);
font-weight: 600;
text-align: center;
background: rgba(12, 12, 16, 0.88);
border-top: 1px solid #2e2e38;
min-height: 42px;
display: flex;
align-items: center;
justify-content: center;
word-break: break-word;
touch-action: manipulation;
cursor: pointer;
}
.empty {
display: none;
position: absolute;
inset: 0;
align-items: center;
justify-content: center;
text-align: center;
color: #666670;
font-size: 16px;
margin: 0;
pointer-events: none;
}
.empty.visible {
display: flex;
}
body.chrome-hidden .toolbar {
max-height: 0;
padding-top: 0;
padding-bottom: 0;
opacity: 0;
transform: translateY(-10px);
border-bottom-color: transparent;
pointer-events: none;
}
body.chrome-hidden .library-panel {
width: 0;
min-width: 0;
border-left-color: transparent;
}
body.chrome-hidden .library-tabs,
body.chrome-hidden .library-pane {
opacity: 0;
pointer-events: none;
}
body.chrome-hidden .tile .remove {
opacity: 0;
pointer-events: none;
}
body.chrome-hidden .stage-slot.empty-slot::before,
body.chrome-hidden .slot-placeholder,
body.chrome-hidden .stage-slot > .caption:empty::before,
body.chrome-hidden .tile .name-badge:empty::before {
opacity: 0;
}
.drag-ghost {
position: fixed;
width: 88px;
height: 118px;
border-radius: 10px;
overflow: hidden;
border: 2px solid #4a6fa5;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
pointer-events: none;
z-index: 1000;
opacity: 0.92;
}
.drag-ghost img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center top;
display: block;
}
.drag-ghost.hidden {
display: none;
}
.modal {
position: fixed;
inset: 0;
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
}
.modal.hidden {
display: none;
}
.modal-backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.72);
}
.modal-card {
position: relative;
width: min(720px, 100%);
max-height: min(92vh, 900px);
overflow: auto;
background: #1e1e26;
border: 1px solid #3a3a48;
border-radius: 16px;
padding: 20px;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
}
.modal-card-small {
width: min(420px, 100%);
}
.modal-title {
margin: 0 0 12px;
font-size: 18px;
font-weight: 600;
}
.modal-copy {
margin: 0 0 12px;
color: #aeb6c8;
font-size: 13px;
line-height: 1.4;
}
.modal-field {
display: block;
margin-bottom: 12px;
color: #c6cada;
font-size: 13px;
}
.modal-field span {
display: block;
margin-bottom: 6px;
}
.label-input {
width: 100%;
padding: 14px 12px;
font-size: 22px;
border-radius: 10px;
border: 2px solid #4a6fa5;
background: #121218;
color: #e8e8ec;
margin-bottom: 14px;
}
.label-input:focus {
outline: none;
border-color: #6b9ee0;
}
.modal-input {
margin-bottom: 0;
font-size: 16px;
}
.number-input {
text-align: center;
font-size: 32px;
font-weight: 700;
letter-spacing: 0.04em;
}
.vkeyboard {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
}
.vk-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
justify-content: center;
}
.vk-key {
min-width: 44px;
height: 48px;
padding: 0 12px;
border-radius: 8px;
border: 1px solid #3a3a48;
background: #2a2a34;
color: #e8e8ec;
font-size: 18px;
font-weight: 600;
cursor: pointer;
touch-action: manipulation;
}
.vk-key:hover {
background: #363642;
}
.vk-key.wide {
min-width: 120px;
}
.vk-key.space {
flex: 1;
min-width: 160px;
max-width: 320px;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
}
/* ---------- ProPresenter config (in library head) ---------- */
.pp-controls {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #2a2a32;
}
.pp-controls-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.pp-controls-title {
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #9a9aa8;
}
.pp-controls-buttons {
display: flex;
gap: 8px;
}
.pp-status {
margin-top: 8px;
min-height: 18px;
font-size: 12px;
color: #9ea7bc;
}
.pp-playlist-select {
margin-top: 8px;
}
/* ---------- Studio displays (hosting the Now/Next page on the LAN) ---------- */
.display-urls {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 8px;
/* A multi-homed machine can offer five or six addresses; keep the list from taking over
the panel on its own. */
max-height: 168px;
overflow-y: auto;
touch-action: pan-y;
}
.display-urls:empty {
display: none;
}
.display-url-row {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 9px;
border: 1px solid #2a2a34;
border-radius: 8px;
background: rgba(26, 26, 32, 0.7);
}
.display-url-row.is-best {
border-color: #4a6fa5;
background: rgba(74, 111, 165, 0.16);
}
.display-url-main {
flex: 1;
min-width: 0;
}
.display-url-text {
font-size: 13px;
font-weight: 700;
color: #cfe0f7;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
}
.display-url-note {
margin-top: 2px;
font-size: 11px;
line-height: 1.35;
color: #8a8a99;
}
.display-url-row.is-best .display-url-note {
color: #9fb6d6;
}
.display-port-row {
display: flex;
align-items: center;
gap: 10px;
margin-top: 8px;
}
.display-port-field {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #a0a0ae;
}
.display-port-field input {
width: 74px;
padding: 6px 8px;
border-radius: 8px;
border: 1px solid #33333d;
background: #0f0f13;
color: #e8e8ec;
font-size: 13px;
}
.display-port-field input:focus {
outline: none;
border-color: #4a6fa5;
}
/* ---------- ProPresenter Now / Next column (show mode) ---------- */
.pp-column {
display: none;
width: clamp(280px, 26vw, 380px);
min-width: 280px;
flex-shrink: 0;
flex-direction: column;
gap: 14px;
padding: 18px 16px;
background: rgba(16, 16, 20, 0.96);
border-left: 1px solid #2a2a32;
overflow: hidden;
}
/* Only present in minimal/show mode, and only when ProPresenter has live data. */
body.chrome-hidden.pp-active .pp-column {
display: flex;
}
.pp-playlist-name {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #8a8a99;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pp-playlist-name:empty {
display: none;
}
.pp-now {
border: 1px solid #30303c;
border-radius: 14px;
background: linear-gradient(160deg, rgba(61, 90, 128, 0.22), rgba(26, 26, 34, 0.6));
padding: 14px 16px;
}
.pp-now-label {
font-size: 11px;
font-weight: 800;
letter-spacing: 0.14em;
text-transform: uppercase;
color: #7fa8e0;
}
.pp-now-name {
margin-top: 6px;
/* Size is set dynamically by fitNowTitle() in renderer.js to fill the box;
this is just the pre-fit default. Keep line-height in sync with that helper. */
font-size: 36px;
font-weight: 700;
line-height: 1.1;
color: #f4f6fb;
/* Wrapping is controlled by fitNowTitle() in renderer.js — it forbids mid-word breaks
and sizes down until whole words fit, so the base must not force break-word. */
overflow-wrap: normal;
word-break: normal;
}
.pp-now-meta {
margin-top: 10px;
font-size: clamp(13px, 1.2vw, 17px);
font-weight: 600;
color: #c6cada;
font-variant-numeric: tabular-nums;
/* Never wrap — a 2nd line would resize the title box and flap the fitted title size. */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pp-progress {
margin-top: 10px;
height: 6px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
overflow: hidden;
}
.pp-progress-fill {
height: 100%;
border-radius: 999px;
background: #4a6fa5;
transition: width 0.25s ease;
}
.pp-next-wrap {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.pp-section-label {
font-size: 11px;
font-weight: 800;
letter-spacing: 0.14em;
text-transform: uppercase;
color: #7a7a8a;
}
.pp-next-list {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: 8px;
overflow: hidden;
}
.pp-next-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
border: 1px solid #2a2a34;
border-radius: 10px;
background: rgba(26, 26, 32, 0.7);
}
.pp-next-name {
flex: 1;
min-width: 0;
font-size: clamp(14px, 1.3vw, 19px);
font-weight: 600;
color: #e2e4ec;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pp-count-chip {
flex-shrink: 0;
min-width: 30px;
padding: 3px 9px;
text-align: center;
border-radius: 999px;
background: rgba(74, 111, 165, 0.28);
color: #cfe0f7;
font-size: 13px;
font-weight: 700;
}
.pp-type-tag {
flex-shrink: 0;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #8a8a99;
}
.pp-next-item.is-header {
background: none;
border: none;
border-bottom: 1px solid #2f2f3a;
border-radius: 0;
padding: 10px 2px 4px;
}
.pp-next-item.is-header .pp-next-name {
font-size: 11px;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #8a8a99;
white-space: normal;
}
.pp-waiting {
font-size: 14px;
color: #7a7a8a;
}
.pp-error {
margin-top: 6px;
font-size: 11px;
color: #d98c8c;
word-break: break-word;
}