The placement canvas sized itself with CSS aspect-ratio plus percentage widths/heights on the widget boxes. aspect-ratio isn't supported on every mobile browser this app gets viewed from, and without it a percentage height on an absolutely-positioned child collapses to 0 against an indeterminate-height ancestor -- the canvas rendered with no visible size at all, just unstyled labels and buttons floating in normal document flow. Confirmed working on desktop, broken on the reporter's phone. Canvas and widget-box geometry is now computed and applied in pixels from JS (measuring the wrap's own width, deriving height from the grid's row/col ratio), recalculated on window resize. Also gives .widget-box's color-mix() background a plain-color fallback for the same class of older-browser gap.
668 lines
20 KiB
CSS
668 lines
20 KiB
CSS
:root {
|
|
--bg: #f5f6f8;
|
|
--surface: #ffffff;
|
|
--surface-alt: #f0f1f4;
|
|
--border: #e3e5e9;
|
|
--text: #16181d;
|
|
--text-muted: #666d7a;
|
|
--accent: #2563eb;
|
|
--accent-hover: #1d4ed8;
|
|
--focus-ring: rgba(37, 99, 235, 0.35);
|
|
--success-bg: #dcfce7;
|
|
--success-text: #166534;
|
|
--danger-bg: #fee2e2;
|
|
--danger-text: #991b1b;
|
|
--warn-bg: #fef9c3;
|
|
--warn-text: #854d0e;
|
|
--shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 1px 3px rgba(16, 24, 40, 0.06);
|
|
--shadow-hover: 0 8px 20px rgba(16, 24, 40, 0.10);
|
|
--overlay: rgba(0, 0, 0, 0.55);
|
|
--overlay-hover: rgba(153, 27, 27, 0.85);
|
|
--color-scheme: light;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root:not([data-theme="light"]) {
|
|
--bg: #0d1016;
|
|
--surface: #161a22;
|
|
--surface-alt: #1d222b;
|
|
--border: #2a2f3a;
|
|
--text: #e8eaed;
|
|
--text-muted: #9aa2b1;
|
|
--accent: #4c8dff;
|
|
--accent-hover: #6ea1ff;
|
|
--focus-ring: rgba(76, 141, 255, 0.4);
|
|
--success-bg: rgba(34, 197, 94, 0.16);
|
|
--success-text: #4ade80;
|
|
--danger-bg: rgba(239, 68, 68, 0.16);
|
|
--danger-text: #f87171;
|
|
--warn-bg: rgba(234, 179, 8, 0.16);
|
|
--warn-text: #fbbf24;
|
|
--shadow: 0 1px 2px rgba(0, 0, 0, 0.35), 0 2px 10px rgba(0, 0, 0, 0.35);
|
|
--shadow-hover: 0 10px 24px rgba(0, 0, 0, 0.45);
|
|
--overlay: rgba(0, 0, 0, 0.55);
|
|
--overlay-hover: rgba(248, 113, 113, 0.35);
|
|
--color-scheme: dark;
|
|
}
|
|
}
|
|
:root[data-theme="dark"] {
|
|
--bg: #0d1016;
|
|
--surface: #161a22;
|
|
--surface-alt: #1d222b;
|
|
--border: #2a2f3a;
|
|
--text: #e8eaed;
|
|
--text-muted: #9aa2b1;
|
|
--accent: #4c8dff;
|
|
--accent-hover: #6ea1ff;
|
|
--focus-ring: rgba(76, 141, 255, 0.4);
|
|
--success-bg: rgba(34, 197, 94, 0.16);
|
|
--success-text: #4ade80;
|
|
--danger-bg: rgba(239, 68, 68, 0.16);
|
|
--danger-text: #f87171;
|
|
--warn-bg: rgba(234, 179, 8, 0.16);
|
|
--warn-text: #fbbf24;
|
|
--shadow: 0 1px 2px rgba(0, 0, 0, 0.35), 0 2px 10px rgba(0, 0, 0, 0.35);
|
|
--shadow-hover: 0 10px 24px rgba(0, 0, 0, 0.45);
|
|
--overlay: rgba(0, 0, 0, 0.55);
|
|
--overlay-hover: rgba(248, 113, 113, 0.35);
|
|
--color-scheme: dark;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
|
|
html {
|
|
color-scheme: var(--color-scheme);
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
-webkit-font-smoothing: antialiased;
|
|
transition: background-color .15s ease, color .15s ease;
|
|
}
|
|
|
|
.page {
|
|
max-width: 1080px;
|
|
margin: 0 auto;
|
|
padding: 28px 20px 72px;
|
|
}
|
|
.page.page-narrow {
|
|
max-width: 420px;
|
|
padding-top: 88px;
|
|
}
|
|
|
|
.topbar {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
margin-bottom: 24px;
|
|
}
|
|
.brand { display: flex; align-items: flex-start; gap: 12px; }
|
|
.brand-mark {
|
|
font-size: 26px;
|
|
line-height: 1;
|
|
margin-top: 2px;
|
|
}
|
|
h1 { font-size: 21px; margin: 0; letter-spacing: -0.01em; font-weight: 700; }
|
|
p.sub { color: var(--text-muted); font-size: 13.5px; margin: 5px 0 0; line-height: 1.5; }
|
|
|
|
.icon-btn {
|
|
flex: none;
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--border);
|
|
background: var(--surface);
|
|
color: var(--text);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
box-shadow: var(--shadow);
|
|
transition: background-color .15s ease, border-color .15s ease, transform .1s ease;
|
|
}
|
|
.icon-btn:hover { background: var(--surface-alt); }
|
|
.icon-btn:active { transform: scale(0.94); }
|
|
|
|
.topbar-actions { display: flex; align-items: center; gap: 14px; }
|
|
.topnav { display: flex; align-items: center; gap: 14px; font-size: 13.5px; }
|
|
.topnav a { color: var(--text-muted); text-decoration: none; }
|
|
.topnav a:hover { color: var(--text); }
|
|
.inline-form { display: inline; margin: 0; }
|
|
button.linklike {
|
|
background: none;
|
|
border: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
color: var(--text-muted);
|
|
font-size: 13.5px;
|
|
font-weight: 400;
|
|
cursor: pointer;
|
|
box-shadow: none;
|
|
}
|
|
button.linklike:hover { color: var(--text); background: none; }
|
|
|
|
.admin-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
|
|
.admin-table th { text-align: left; color: var(--text-muted); font-weight: 600; padding: 6px 8px 6px 0; border-bottom: 1px solid var(--border); }
|
|
.admin-table td { padding: 8px 8px 8px 0; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
.admin-actions form { margin: 4px 0 0; }
|
|
.admin-actions details summary { cursor: pointer; color: var(--text-muted); font-size: 13px; }
|
|
.admin-actions input[type="password"] { margin-top: 6px; }
|
|
.admin-frame { border-bottom: 1px solid var(--border); padding: 10px 0; }
|
|
.admin-frame:last-child { border-bottom: none; }
|
|
.admin-inline-form { display: flex; gap: 8px; align-items: center; margin-top: 6px; }
|
|
.admin-inline-form input[type="text"] { margin-top: 0; flex: 1; }
|
|
|
|
h2.card-title, summary.card-title {
|
|
font-size: 14.5px;
|
|
font-weight: 650;
|
|
margin: 0 0 14px;
|
|
letter-spacing: 0.01em;
|
|
text-transform: uppercase;
|
|
color: var(--text-muted);
|
|
}
|
|
summary.card-title { cursor: pointer; margin-bottom: 0; }
|
|
details.card[open] summary.card-title { margin-bottom: 14px; }
|
|
details.card .sub { margin-top: 8px; }
|
|
|
|
.palette-table-wrap { overflow-x: auto; margin-top: 14px; }
|
|
.palette-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
.palette-table th {
|
|
text-align: left;
|
|
color: var(--text-muted);
|
|
font-weight: 600;
|
|
font-size: 11.5px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.02em;
|
|
padding: 0 8px 8px 0;
|
|
}
|
|
.palette-table td { padding: 4px 8px 4px 0; vertical-align: middle; }
|
|
.palette-table input { margin-top: 0; }
|
|
.palette-swatch-preview {
|
|
display: block;
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: 6px;
|
|
border: 1px solid var(--border);
|
|
}
|
|
.palette-table input.palette-hex {
|
|
width: 92px;
|
|
font-family: ui-monospace, "SF Mono", Consolas, monospace;
|
|
text-transform: lowercase;
|
|
}
|
|
.palette-table input.palette-rgb {
|
|
width: 58px;
|
|
}
|
|
|
|
.slider-value {
|
|
float: right;
|
|
font-weight: 400;
|
|
color: var(--text-muted);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
input[type="range"] {
|
|
width: 100%;
|
|
margin-top: 8px;
|
|
accent-color: var(--accent);
|
|
padding: 0;
|
|
background: none;
|
|
}
|
|
|
|
.preview-compare {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 14px;
|
|
margin-top: 14px;
|
|
}
|
|
.preview-img {
|
|
width: 100%;
|
|
display: block;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--border);
|
|
background: var(--surface-alt);
|
|
min-height: 100px;
|
|
}
|
|
|
|
.card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 14px;
|
|
padding: 20px 22px 22px;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
.card + .card { margin-top: 20px; }
|
|
|
|
label { display: block; margin-top: 16px; font-size: 13px; font-weight: 600; color: var(--text); }
|
|
label:first-child { margin-top: 0; }
|
|
|
|
input, select {
|
|
width: 100%;
|
|
padding: 9px 10px;
|
|
box-sizing: border-box;
|
|
margin-top: 5px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: inherit;
|
|
transition: border-color .15s ease, box-shadow .15s ease;
|
|
}
|
|
input:focus, select:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px var(--focus-ring);
|
|
}
|
|
|
|
.checkbox-row { display: flex; align-items: center; gap: 8px; margin-top: 16px; }
|
|
.checkbox-row input { width: auto; margin-top: 0; }
|
|
.checkbox-row label { margin-top: 0; font-weight: normal; }
|
|
|
|
.calendar-user-list { list-style: none; margin: 12px 0 0; padding: 0; }
|
|
.calendar-user-list > li { margin-top: 14px; }
|
|
.calendar-user-list > li:first-child { margin-top: 0; }
|
|
.calendar-user-name { margin: 0; font-size: 13px; font-weight: 600; color: var(--text); }
|
|
.calendar-row { flex-wrap: wrap; }
|
|
.calendar-color-picker { display: inline-flex; align-items: center; gap: 5px; margin-left: 8px; }
|
|
.color-swatch {
|
|
width: 20px; height: 20px; padding: 0; margin: 0;
|
|
border: 2px solid var(--border); border-radius: 5px;
|
|
box-shadow: none; cursor: pointer;
|
|
}
|
|
.color-swatch.selected { border-color: var(--text); box-shadow: 0 0 0 1.5px var(--text); }
|
|
.color-swatch-auto {
|
|
width: auto; height: 20px; padding: 0 6px; font-size: 10px; font-weight: 600;
|
|
color: var(--text-muted); background: var(--surface-alt);
|
|
}
|
|
.color-swatch-auto.selected { color: var(--text); }
|
|
|
|
button {
|
|
margin-top: 20px;
|
|
padding: 10px 16px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
background: var(--accent);
|
|
color: white;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
font-family: inherit;
|
|
transition: background-color .15s ease, transform .1s ease;
|
|
}
|
|
button:hover { background: var(--accent-hover); }
|
|
button:active { transform: translateY(1px); }
|
|
button.btn-inline {
|
|
margin-top: 0;
|
|
padding: 3px 10px;
|
|
font-size: 12px;
|
|
vertical-align: middle;
|
|
}
|
|
button.secondary {
|
|
background: transparent;
|
|
color: var(--text);
|
|
border: 1px solid var(--border);
|
|
margin-right: 8px;
|
|
}
|
|
button.secondary:hover { background: var(--surface-alt); }
|
|
|
|
.status { margin-top: 16px; padding: 10px 12px; border-radius: 8px; font-size: 14px; }
|
|
.status.ok { background: var(--success-bg); color: var(--success-text); }
|
|
.status.err { background: var(--danger-bg); color: var(--danger-text); }
|
|
.info-box {
|
|
margin-bottom: 20px;
|
|
padding: 12px 14px;
|
|
border-radius: 10px;
|
|
font-size: 13px;
|
|
background: var(--surface-alt);
|
|
color: var(--text-muted);
|
|
border: 1px solid var(--border);
|
|
}
|
|
.info-box.warn { background: var(--warn-bg); color: var(--warn-text); border-color: transparent; }
|
|
|
|
#widget-canvas-wrap {
|
|
/* Height is set in px by frame_layout.js (measured wrap width * rows/cols)
|
|
-- not CSS aspect-ratio, which isn't supported on every mobile browser
|
|
this app gets viewed from, and percentage heights on the widget boxes
|
|
below would silently collapse to 0 against an indeterminate-height
|
|
ancestor if it weren't. Box positions/sizes are likewise set in px by
|
|
JS, not CSS percentages, for the same cross-browser reason. */
|
|
width: 100%;
|
|
max-width: 640px;
|
|
box-sizing: border-box;
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
background-color: var(--surface-alt);
|
|
background-repeat: repeat;
|
|
}
|
|
#widget-canvas { position: relative; width: 100%; height: 100%; }
|
|
.widget-box {
|
|
position: absolute;
|
|
box-sizing: border-box;
|
|
border: 2px solid var(--accent);
|
|
background: var(--surface-alt); /* fallback for browsers without color-mix() support */
|
|
background: color-mix(in srgb, var(--accent) 14%, var(--surface));
|
|
border-radius: 6px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
touch-action: none;
|
|
cursor: grab;
|
|
user-select: none;
|
|
overflow: hidden;
|
|
}
|
|
.widget-box.dragging { cursor: grabbing; box-shadow: var(--shadow-hover); z-index: 2; }
|
|
.widget-box-label {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
pointer-events: none;
|
|
}
|
|
.widget-box-remove {
|
|
position: absolute;
|
|
top: 4px;
|
|
right: 4px;
|
|
width: 20px;
|
|
height: 20px;
|
|
padding: 0;
|
|
margin: 0;
|
|
line-height: 1;
|
|
border-radius: 50%;
|
|
border: none;
|
|
background: var(--overlay);
|
|
color: #fff;
|
|
cursor: pointer;
|
|
}
|
|
.widget-box-remove:hover { background: var(--overlay-hover); }
|
|
.widget-box-resize-handle {
|
|
position: absolute;
|
|
bottom: 0;
|
|
right: 0;
|
|
width: 16px;
|
|
height: 16px;
|
|
cursor: nwse-resize;
|
|
touch-action: none;
|
|
border-right: 3px solid var(--accent);
|
|
border-bottom: 3px solid var(--accent);
|
|
border-bottom-right-radius: 4px;
|
|
}
|
|
|
|
code {
|
|
background: var(--surface-alt);
|
|
color: var(--text);
|
|
padding: 2px 5px;
|
|
border-radius: 4px;
|
|
font-size: 0.92em;
|
|
}
|
|
.info-box code, .info-box.warn code { background: rgba(127, 127, 127, 0.18); color: inherit; }
|
|
|
|
.thumb { width: 160px; max-width: 100%; border-radius: 8px; display: block; }
|
|
|
|
.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 10px; margin-top: 8px; }
|
|
.photo-card {
|
|
position: relative; cursor: grab; border-radius: 8px; overflow: hidden; aspect-ratio: 1;
|
|
background: var(--surface-alt); border: 1px solid var(--border);
|
|
box-shadow: var(--shadow);
|
|
transition: box-shadow .15s ease, transform .15s ease;
|
|
/* pan-y (not none): lets a normal touch-scroll of the page work
|
|
when you touch a card without meaning to drag it. Dragging on
|
|
touch instead requires a brief hold first (see the JS below),
|
|
which switches this to "none" for the rest of that touch --
|
|
only once we're sure it's a deliberate drag, not a scroll. */
|
|
touch-action: pan-y;
|
|
/* Without this, a press-and-drag gesture also triggers the
|
|
browser's native text/content selection (the blue highlight) --
|
|
distracting, and on some browsers it fights the pointer-based
|
|
drag tracking below closely enough to break it outright. */
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
}
|
|
.photo-card:hover { box-shadow: var(--shadow-hover); transform: translateY(-1px); }
|
|
.photo-card:active { cursor: grabbing; }
|
|
.photo-card.drag-armed {
|
|
box-shadow: 0 0 0 3px var(--focus-ring) inset;
|
|
transform: scale(0.97);
|
|
}
|
|
/* No transform transition here -- the JS drives transform on every
|
|
pointermove to track the finger 1:1, and the .15s base transition
|
|
would otherwise make it visibly lag behind a fast swipe. */
|
|
.photo-card.dragging {
|
|
z-index: 20;
|
|
box-shadow: 0 16px 32px rgba(0, 0, 0, 0.35);
|
|
transition: box-shadow .15s ease;
|
|
pointer-events: none; /* so elementFromPoint hits the card underneath, not this one */
|
|
cursor: grabbing;
|
|
}
|
|
.photo-card.drag-over { outline: 3px solid var(--accent); outline-offset: -3px; }
|
|
.photo-card img { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
|
|
.photo-card .badge { position: absolute; top: 6px; left: 6px; background: var(--overlay); color: white; font-size: 10px; padding: 2px 6px; border-radius: 4px; }
|
|
.photo-card .remove-btn, .thumb-wrap .remove-btn {
|
|
position: absolute; top: 6px; right: 6px; width: 22px; height: 22px; margin: 0; padding: 0;
|
|
line-height: 20px; text-align: center; font-size: 13px; border-radius: 50%;
|
|
background: var(--overlay); color: white;
|
|
}
|
|
.photo-card .remove-btn:hover, .thumb-wrap .remove-btn:hover { background: var(--overlay-hover); }
|
|
.photo-card .show-next {
|
|
position: absolute; bottom: 6px; left: 6px; right: 6px; margin: 0; padding: 5px 0;
|
|
font-size: 11px; text-align: center; background: rgba(37, 99, 235, 0.85); border-radius: 4px;
|
|
}
|
|
.thumb-wrap { position: relative; display: inline-block; }
|
|
|
|
.layout {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
|
|
gap: 20px;
|
|
align-items: start;
|
|
}
|
|
.main-col, .side-col { display: flex; flex-direction: column; }
|
|
@media (max-width: 860px) {
|
|
.layout { grid-template-columns: 1fr; }
|
|
}
|
|
/* ------------------------------------------------------------------ */
|
|
/* App shell: left sidebar (frame list + account nav) + main content. */
|
|
/* Used by app_base.html for all logged-in pages; the narrow auth/ */
|
|
/* manage pages keep the simple centered .page layout above. */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
.shell { display: flex; min-height: 100vh; }
|
|
|
|
.sidebar {
|
|
width: 248px;
|
|
flex: none;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--surface);
|
|
border-right: 1px solid var(--border);
|
|
padding: 20px 14px 16px;
|
|
position: sticky;
|
|
top: 0;
|
|
height: 100vh;
|
|
overflow-y: auto;
|
|
}
|
|
.sidebar .brand { display: flex; align-items: center; gap: 10px; padding: 0 8px 18px; }
|
|
.sidebar .brand h1 { font-size: 17px; }
|
|
.sidebar-section {
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--text-muted);
|
|
padding: 14px 8px 6px;
|
|
}
|
|
.sidebar a.nav-item, .sidebar .nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 9px 10px;
|
|
border-radius: 8px;
|
|
color: var(--text);
|
|
text-decoration: none;
|
|
font-size: 14px;
|
|
transition: background-color .12s ease;
|
|
}
|
|
.sidebar a.nav-item:hover { background: var(--surface-alt); }
|
|
.sidebar a.nav-item.active { background: var(--surface-alt); font-weight: 600; }
|
|
.nav-item .frame-dot {
|
|
width: 8px; height: 8px; border-radius: 50%; flex: none;
|
|
background: var(--text-muted); opacity: 0.5;
|
|
}
|
|
.nav-item.online .frame-dot { background: #22c55e; opacity: 1; }
|
|
.nav-item .nav-sub { margin-left: auto; font-size: 11.5px; color: var(--text-muted); }
|
|
.sidebar-footer { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--border); }
|
|
.sidebar-footer .nav-item { color: var(--text-muted); font-size: 13.5px; }
|
|
.sidebar-footer form { margin: 0; }
|
|
.sidebar-footer button.linklike {
|
|
display: block; width: 100%; text-align: left; padding: 9px 10px; border-radius: 8px;
|
|
}
|
|
.sidebar-footer button.linklike:hover { background: var(--surface-alt); }
|
|
|
|
.main {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 24px 28px 72px;
|
|
max-width: 1160px;
|
|
}
|
|
|
|
.page-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
margin-bottom: 18px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.page-head h1 { font-size: 21px; margin: 0; letter-spacing: -0.01em; font-weight: 700; }
|
|
.page-head .head-actions { display: flex; align-items: center; gap: 10px; }
|
|
|
|
.tabs {
|
|
display: flex;
|
|
gap: 4px;
|
|
margin-bottom: 20px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.tabs a {
|
|
padding: 9px 14px;
|
|
font-size: 14px;
|
|
color: var(--text-muted);
|
|
text-decoration: none;
|
|
border-bottom: 2px solid transparent;
|
|
margin-bottom: -1px;
|
|
transition: color .12s ease;
|
|
}
|
|
.tabs a:hover { color: var(--text); }
|
|
.tabs a.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
|
|
.tabs a.tab-disabled { opacity: 0.45; }
|
|
.tabs a.tab-disabled:hover { opacity: 0.7; }
|
|
|
|
.frame-name-view { display: inline-flex; align-items: center; gap: 6px; }
|
|
.frame-name-pencil {
|
|
background: none;
|
|
border: none;
|
|
box-shadow: none;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
line-height: 1;
|
|
padding: 4px;
|
|
margin: 0;
|
|
opacity: 0.55;
|
|
color: var(--text);
|
|
transition: opacity .12s ease, background-color .12s ease;
|
|
}
|
|
.frame-name-pencil:hover { opacity: 1; background: var(--surface-alt); border-radius: 6px; }
|
|
.frame-name-edit { display: inline-flex; align-items: center; gap: 6px; }
|
|
.frame-name-edit input {
|
|
width: auto;
|
|
margin-top: 0;
|
|
padding: 5px 8px;
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
}
|
|
.frame-name-edit button { margin-top: 0; }
|
|
|
|
.control-banner {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 18px;
|
|
padding: 10px 14px;
|
|
border-radius: 10px;
|
|
font-size: 13.5px;
|
|
background: var(--warn-bg);
|
|
color: var(--warn-text);
|
|
}
|
|
.control-banner button { margin: 0; }
|
|
|
|
/* Always-visible device summary, sitting between the page title and the
|
|
tabs (see _device_status_bar.html) -- a compact horizontal row rather
|
|
than a full .card, since it has to fit above the tabs on every frame
|
|
page without pushing content down. */
|
|
.device-status-bar {
|
|
padding: 10px 16px;
|
|
margin-bottom: 18px;
|
|
}
|
|
.device-status-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: baseline;
|
|
column-gap: 26px;
|
|
row-gap: 6px;
|
|
}
|
|
.device-status-row .sub { margin: 0; }
|
|
.device-stat {
|
|
font-size: 13px;
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
}
|
|
.device-stat strong { color: var(--text); font-weight: 600; }
|
|
.device-stat.alert, .device-stat.alert strong { color: var(--danger-text); }
|
|
@media (max-width: 860px) {
|
|
.device-status-row { column-gap: 16px; }
|
|
}
|
|
|
|
/* Mobile: sidebar off-canvas, hamburger in a slim top bar. */
|
|
.mobile-bar { display: none; }
|
|
.sidebar-backdrop { display: none; }
|
|
@media (max-width: 860px) {
|
|
.mobile-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 30;
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 10px 14px;
|
|
}
|
|
.mobile-bar .brand { display: flex; align-items: center; gap: 8px; }
|
|
.mobile-bar h1 { font-size: 16px; margin: 0; }
|
|
.mobile-bar .icon-btn { width: 34px; height: 34px; box-shadow: none; }
|
|
.mobile-bar .spacer { flex: 1; }
|
|
|
|
.shell { display: block; }
|
|
.sidebar {
|
|
position: fixed;
|
|
left: 0; top: 0; bottom: 0;
|
|
z-index: 50;
|
|
height: 100vh;
|
|
transform: translateX(-105%);
|
|
transition: transform .2s ease;
|
|
box-shadow: var(--shadow-hover);
|
|
}
|
|
.shell.sidebar-open .sidebar { transform: translateX(0); }
|
|
.sidebar-backdrop {
|
|
position: fixed; inset: 0; z-index: 40;
|
|
background: var(--overlay);
|
|
opacity: 0; pointer-events: none;
|
|
transition: opacity .2s ease;
|
|
}
|
|
.shell.sidebar-open .sidebar-backdrop { display: block; opacity: 1; pointer-events: auto; }
|
|
.sidebar-backdrop { display: block; }
|
|
|
|
.main { padding: 18px 14px 64px; }
|
|
}
|