The paragraph under the PIN fields explained what the PIN covers and how to clear it by hand — and only someone already past the PIN could read it, so the recovery note was never where it was needed. Gone, with the dead style and the element lookup that hung off it. The live line under the heading stays: it is the only thing that says whether admin is locked, and the only warning that a passcode in the server's environment outranks whatever is set here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
553 lines
16 KiB
CSS
553 lines
16 KiB
CSS
/* ProdDash admin page — on top of the shell's style.css */
|
|
|
|
body { overflow: auto; }
|
|
|
|
.admin-tag {
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--muted);
|
|
border: 1px solid var(--border);
|
|
border-radius: 5px;
|
|
padding: 2px 6px;
|
|
margin-left: 6px;
|
|
vertical-align: 2px;
|
|
}
|
|
|
|
#admin-main {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
width: min(720px, 100%);
|
|
margin: 0 auto;
|
|
padding: 18px 16px 60px;
|
|
}
|
|
|
|
.admin-h1 {
|
|
font-size: 16px;
|
|
margin: 22px 0 6px;
|
|
}
|
|
|
|
.admin-copy {
|
|
color: var(--muted);
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.admin-storage { margin-top: -6px; font-size: 12px; }
|
|
.admin-storage code {
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
font-size: 11.5px;
|
|
color: var(--text);
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 5px;
|
|
padding: 1px 5px;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.admin-card {
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 14px 16px;
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.admin-card h2 { font-size: 14px; margin-bottom: 8px; }
|
|
|
|
.login-row { display: flex; gap: 8px; }
|
|
.login-row input {
|
|
flex: 1;
|
|
padding: 8px 10px;
|
|
font-size: 14px;
|
|
color: var(--text);
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 7px;
|
|
}
|
|
.login-row input:focus { outline: none; border-color: var(--accent); }
|
|
|
|
.module-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.module-head .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none; }
|
|
.module-head .dot.ok { background: var(--accent); box-shadow: 0 0 5px var(--accent); }
|
|
.module-head .dot.connecting { background: var(--warn); }
|
|
.module-head .dot.error { background: var(--danger); }
|
|
|
|
.module-name { font-size: 14px; font-weight: 700; }
|
|
.module-version { color: var(--muted); font-size: 11px; }
|
|
.module-head .spacer { flex: 1; }
|
|
|
|
.module-desc { color: var(--muted); font-size: 12px; margin: 6px 0 2px; }
|
|
|
|
.module-health {
|
|
font-size: 12px;
|
|
color: var(--muted);
|
|
margin-top: 4px;
|
|
min-height: 15px;
|
|
}
|
|
.module-health.error { color: var(--danger); }
|
|
|
|
/* a module card's own status line ("polling timers on 10.3.11.20:1025",
|
|
"no ProPresenter host configured"): a readout, so it sits in a bordered
|
|
monospace box a shade lighter than the card; ok / connecting / error
|
|
show as the text (and border) colour, the box itself never changes */
|
|
.module-status {
|
|
display: inline-block;
|
|
max-width: 100%;
|
|
margin-top: 8px;
|
|
padding: 4px 8px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
background: color-mix(in srgb, var(--text) 5%, var(--panel));
|
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
font-size: 11.5px;
|
|
line-height: 1.45;
|
|
color: var(--muted);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.module-status:empty { display: none; }
|
|
.module-status.ok { color: var(--text); }
|
|
.module-status.connecting {
|
|
color: var(--warn);
|
|
border-color: color-mix(in srgb, var(--warn) 40%, var(--border));
|
|
}
|
|
.module-status.error {
|
|
color: var(--danger);
|
|
border-color: color-mix(in srgb, var(--danger) 40%, var(--border));
|
|
}
|
|
|
|
.module-mount-error {
|
|
margin-top: 8px;
|
|
padding: 8px 10px;
|
|
border: 1px solid var(--danger);
|
|
border-radius: 8px;
|
|
color: var(--danger);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.module-form { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 12px; }
|
|
.module-form .field { max-width: 420px; }
|
|
|
|
/* a multi-line string (manifest type `text`): the same box as the other
|
|
inputs (those rules live in the shell's style.css), a few lines tall */
|
|
.field textarea {
|
|
padding: 7px 9px;
|
|
font-size: 13px;
|
|
font-family: inherit;
|
|
line-height: 1.4;
|
|
color: var(--text);
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 7px;
|
|
resize: vertical;
|
|
min-height: 72px;
|
|
}
|
|
.field textarea:focus { outline: none; border-color: var(--accent); }
|
|
|
|
/* endpoint fields: every module's upstream is entered the same way */
|
|
.field .endpoint-row { display: flex; align-items: center; gap: 7px; }
|
|
.endpoint-row .ep-host { flex: 1; min-width: 0; }
|
|
.endpoint-row .ep-port { width: 92px; flex: none; }
|
|
.endpoint-row .ep-sep { color: var(--muted); font-weight: 600; }
|
|
/* several picks from a list (manifest type `multiselect`) */
|
|
.field.multiselect-field { cursor: default; }
|
|
.field.multiselect-field .multiselect { display: flex; flex-direction: column; gap: 6px; }
|
|
.multiselect-row { display: flex; align-items: center; gap: 8px; cursor: pointer; }
|
|
.multiselect-empty { color: var(--muted); font-size: 12px; }
|
|
|
|
.module-form-empty { color: var(--muted); font-size: 12px; margin-top: 10px; }
|
|
|
|
/* labeled config subsections (manifest field `group`) */
|
|
.field-group {
|
|
margin: 14px 0 4px;
|
|
padding: 12px 14px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 9px;
|
|
}
|
|
.field-group-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
position: relative; /* the ⓘ popover hangs off this row */
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--muted);
|
|
margin-bottom: 10px;
|
|
}
|
|
.field-group-name { min-width: 0; }
|
|
.field-group .field:last-child { margin-bottom: 0; }
|
|
|
|
/* group help (configGroups[name].help): an ⓘ beside the title. Hover or
|
|
focus peeks at the text; a click pins it open until the next click. */
|
|
.group-info { display: inline-flex; align-items: center; }
|
|
.group-info-btn {
|
|
width: 16px;
|
|
height: 16px;
|
|
padding: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px solid var(--muted);
|
|
border-radius: 50%;
|
|
background: transparent;
|
|
color: var(--muted);
|
|
font: italic 700 10px/1 ui-serif, Georgia, "Times New Roman", serif;
|
|
text-transform: none;
|
|
letter-spacing: 0;
|
|
cursor: help;
|
|
}
|
|
.group-info-btn:hover,
|
|
.group-info.is-open .group-info-btn { color: var(--text); border-color: var(--text); }
|
|
.group-info-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
.group-info-pop {
|
|
display: none;
|
|
position: absolute;
|
|
left: 0;
|
|
top: calc(100% + 6px);
|
|
z-index: 5;
|
|
max-width: min(440px, 100%);
|
|
padding: 8px 10px;
|
|
background: color-mix(in srgb, var(--text) 6%, var(--panel));
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
color: var(--text);
|
|
font-size: 12px;
|
|
font-weight: 400;
|
|
line-height: 1.5;
|
|
letter-spacing: normal;
|
|
text-transform: none;
|
|
white-space: normal;
|
|
cursor: auto;
|
|
}
|
|
/* keeps the popover open while the pointer crosses the gap down to it */
|
|
.group-info-pop::before { content: ""; position: absolute; left: 0; right: 0; top: -8px; height: 8px; }
|
|
.group-info:hover .group-info-pop,
|
|
.group-info-btn:focus-visible + .group-info-pop,
|
|
.group-info.is-open .group-info-pop { display: block; }
|
|
|
|
/* group toggle (configGroups[name].toggle): the named switch sits at the
|
|
right of the title row as the group's on/off; the body dims while it is
|
|
off but stays editable */
|
|
.field-group-toggle {
|
|
margin-left: auto;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
flex: none;
|
|
}
|
|
.field-group-toggle .field {
|
|
margin: 0;
|
|
max-width: none;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
letter-spacing: normal;
|
|
text-transform: none;
|
|
color: var(--text);
|
|
}
|
|
.field-group-toggle .field.switch-field { flex-direction: row-reverse; gap: 8px; }
|
|
.field-group-toggle .field.switch-field .switch-label { font-size: 12px; }
|
|
.field-group-toggle .field.check { flex-direction: row-reverse; gap: 8px; }
|
|
.field-group.is-off > .field-group-body { opacity: 0.45; transition: opacity 0.15s; }
|
|
.field-group.has-toggle > .field-group-body { transition: opacity 0.15s; }
|
|
|
|
/* collapsible groups (manifest configGroups[name].collapsed) */
|
|
details.field-group > summary.field-group-title {
|
|
cursor: pointer;
|
|
list-style: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
margin-bottom: 0;
|
|
user-select: none;
|
|
}
|
|
details.field-group > summary.field-group-title::-webkit-details-marker { display: none; }
|
|
details.field-group > summary.field-group-title::before {
|
|
content: "";
|
|
width: 0; height: 0;
|
|
border-left: 5px solid var(--muted);
|
|
border-top: 4px solid transparent;
|
|
border-bottom: 4px solid transparent;
|
|
transition: transform 0.15s;
|
|
}
|
|
details.field-group[open] > summary.field-group-title::before { transform: rotate(90deg); }
|
|
details.field-group[open] > summary.field-group-title { margin-bottom: 10px; }
|
|
details.field-group > summary.field-group-title:hover { color: var(--text); }
|
|
|
|
/* multi-column groups (configGroups[name].columns): a neat checklist grid */
|
|
.field-group-body.is-columns {
|
|
display: grid;
|
|
grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
|
|
gap: 4px 16px;
|
|
}
|
|
.field-group-body.is-columns .field { margin-bottom: 4px; max-width: none; }
|
|
@media (max-width: 560px) { .field-group-body.is-columns { grid-template-columns: 1fr 1fr; } }
|
|
|
|
/* one line of guidance under a control (field `help`) */
|
|
.field-help {
|
|
display: block;
|
|
font-size: 11.5px;
|
|
line-height: 1.45;
|
|
color: var(--muted);
|
|
margin-top: 2px;
|
|
}
|
|
.field-help code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
|
|
/* showWhen: a hidden dependent field must actually collapse — `.field`'s
|
|
own `display:flex` (an author rule) otherwise outranks the [hidden] UA
|
|
default and the field stays visible. */
|
|
.module-form .field[hidden] { display: none; }
|
|
|
|
/* a boolean rendered as a toggle (manifest type `switch`) */
|
|
.field.switch-field {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 10px;
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
max-width: none;
|
|
}
|
|
.field.switch-field .switch-label { font-size: 13px; }
|
|
|
|
/* the card's one action row: status on the left, Configure / Apply on the right */
|
|
.module-actions {
|
|
display: flex;
|
|
align-items: flex-end; /* the button stays bottom-right when a readout wraps */
|
|
gap: 10px;
|
|
margin-top: 10px;
|
|
}
|
|
.module-name.is-toggle { cursor: pointer; }
|
|
.module-name.is-toggle:hover,
|
|
.module-name.is-toggle:focus-visible { color: var(--accent); outline: none; }
|
|
.module-actions .admin-status { flex: 1; margin-top: 0; }
|
|
.module-actions .module-status { flex: 0 1 auto; min-width: 0; margin-top: 0; }
|
|
.module-actions .btn { flex: none; margin-left: auto; }
|
|
|
|
.admin-status { min-height: 16px; font-size: 12px; color: var(--muted); margin-top: 8px; }
|
|
.admin-status.error { color: var(--danger); }
|
|
.admin-status.ok { color: var(--accent); }
|
|
|
|
/* enable/disable switch */
|
|
.switch {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
color: var(--muted);
|
|
user-select: none;
|
|
}
|
|
.switch input { position: absolute; opacity: 0; pointer-events: none; }
|
|
.switch .track {
|
|
width: 34px;
|
|
height: 19px;
|
|
border-radius: 999px;
|
|
background: var(--border);
|
|
position: relative;
|
|
transition: background 0.2s;
|
|
flex: none;
|
|
}
|
|
.switch .track::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: 2px; left: 2px;
|
|
width: 15px; height: 15px;
|
|
border-radius: 50%;
|
|
background: var(--muted);
|
|
transition: transform 0.2s, background 0.2s;
|
|
}
|
|
.switch input:checked + .track { background: var(--accent); }
|
|
.switch input:checked + .track::after { transform: translateX(15px); background: var(--bg); }
|
|
|
|
.layout-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 9px 12px;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
margin-bottom: 8px;
|
|
}
|
|
.layout-row .name { flex: 1; font-size: 13px; font-weight: 600; }
|
|
.layout-row .meta { color: var(--muted); font-size: 11px; }
|
|
.layouts-empty { color: var(--muted); font-size: 13px; }
|
|
|
|
/* ── version, update banner, catalog ───────────────────────────────── */
|
|
|
|
.admin-version {
|
|
font-size: 11px;
|
|
color: var(--muted);
|
|
margin-left: 4px;
|
|
vertical-align: 2px;
|
|
}
|
|
|
|
.update-card {
|
|
border-color: color-mix(in srgb, var(--warn) 55%, var(--border));
|
|
background: color-mix(in srgb, var(--warn) 7%, var(--panel));
|
|
margin-top: 18px;
|
|
}
|
|
.update-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.update-head strong { font-size: 14px; }
|
|
.update-head .spacer { flex: 1; }
|
|
.update-card .admin-copy { margin: 8px 0 0; }
|
|
|
|
.btn.small { padding: 4px 9px; font-size: 12px; }
|
|
.btn.danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); }
|
|
.btn.danger:hover { background: color-mix(in srgb, var(--danger) 14%, var(--bg)); }
|
|
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }
|
|
a.btn { text-decoration: none; }
|
|
|
|
.module-source {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
color: var(--muted);
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
padding: 1px 5px;
|
|
}
|
|
.module-update-slot:empty { display: none; }
|
|
|
|
.catalog-toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.catalog-toolbar .admin-status { margin-top: 0; }
|
|
|
|
.catalog-row {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 12px 14px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.catalog-main { flex: 1; min-width: 0; }
|
|
.catalog-title { display: flex; align-items: center; gap: 8px; }
|
|
.catalog-row .module-desc { margin: 4px 0 2px; }
|
|
.catalog-row .module-health { margin-top: 2px; }
|
|
|
|
/* ── theme ──────────────────────────────────────────────────────────── */
|
|
|
|
/* one swatch per preset: a miniature dashboard (page, a tile on it, the
|
|
accent as a status dot) and the name; the current one wears the accent */
|
|
.theme-swatches {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
|
|
gap: 10px;
|
|
margin-bottom: 6px;
|
|
}
|
|
.theme-swatch {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 7px;
|
|
padding: 8px;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
color: var(--text);
|
|
font: inherit;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
.theme-swatch:hover { border-color: var(--muted); }
|
|
.theme-swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
.theme-swatch.is-current { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
|
|
.theme-preview {
|
|
position: relative;
|
|
display: block;
|
|
height: 46px;
|
|
border-radius: 6px;
|
|
background: var(--sw-bg);
|
|
border: 1px solid color-mix(in srgb, var(--sw-panel) 60%, var(--sw-accent));
|
|
overflow: hidden;
|
|
}
|
|
.theme-preview::before {
|
|
content: "";
|
|
position: absolute;
|
|
left: 8px; right: 8px; top: 10px; bottom: 8px;
|
|
border-radius: 4px;
|
|
background: var(--sw-panel);
|
|
border: 1px solid color-mix(in srgb, var(--sw-panel) 75%, var(--sw-accent));
|
|
}
|
|
.theme-preview::after {
|
|
content: "";
|
|
position: absolute;
|
|
left: 14px; top: 16px;
|
|
width: 8px; height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--sw-accent);
|
|
box-shadow: 0 0 6px var(--sw-accent);
|
|
}
|
|
.theme-name { display: flex; align-items: center; gap: 6px; }
|
|
.theme-swatch.is-current .theme-name::after {
|
|
content: "current";
|
|
margin-left: auto;
|
|
font-size: 9.5px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
color: var(--accent);
|
|
}
|
|
#theme-status { margin-top: 4px; }
|
|
|
|
/* ── the admin PIN ── */
|
|
|
|
/* Digits, evenly spaced, so six of them read as a PIN rather than a word. */
|
|
.pin-input {
|
|
font-variant-numeric: tabular-nums;
|
|
letter-spacing: 0.35em;
|
|
text-align: center;
|
|
max-width: 9em;
|
|
}
|
|
.pin-form {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: flex-end;
|
|
gap: 12px;
|
|
}
|
|
.pin-field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
font-size: 12px;
|
|
color: var(--muted);
|
|
}
|
|
.pin-field[hidden] { display: none; }
|
|
.pin-field input {
|
|
padding: 8px 10px;
|
|
font-size: 14px;
|
|
color: var(--text);
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 7px;
|
|
}
|
|
.pin-field input:focus { outline: none; border-color: var(--accent); }
|
|
.pin-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-left: auto;
|
|
}
|