Three things the dashboard was making people work for. **The menu handle.** It was the ProdDash wordmark on a pill hanging past the bar's bottom edge, which read as a step cut out of the bar. The wordmark goes back in the bar where it belongs, and the handle becomes a small accent lip on the bar's bottom edge — the same idea as a tile's notch — that grows under the pointer so it stays easy to hit without being loud at rest. That also fixes the animation Safari made a meal of: the old handle transitioned font-size and line-height, animating text metrics frame by frame on the way out. Nothing here animates text. The lip rides `--topbar-h`, the bar's live height, so it tracks the bar's own collapse instead of running a second animation against it, and its hover growth is width, height and opacity only. **Fullscreen.** Fullscreen is for watching, not arranging, so every tile's title bar now folds away on the way in. A notch still peeks one open, and it folds back ten seconds after the pointer leaves that tile — coming back to the tile cancels the countdown. Peeking is a transient class, so a tile's own hidden/shown preference is untouched and comes back with the screen. **Tile settings.** Gear settings are display choices — a text size, a filter, a colour — so they apply as they are changed and save with the layout. Apply and Cancel are gone; close by clicking away, pressing Escape, or the gear again. Typing is debounced so a remount doesn't land on every keystroke, and an unchanged value remounts nothing (a text field fires input and then change for one edit). The module guide says so now too. Verified in the browser: settings apply live and persist, no-op changes cost nothing, all three close paths work, and in fullscreen both heads fold, one peeks, it folds back at ten seconds, returning the pointer cancels it, and leaving fullscreen restores everything with no peeks left behind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
760 lines
20 KiB
CSS
760 lines
20 KiB
CSS
/* ProdDash shell — dark, high-contrast, production-booth friendly.
|
|
Visual language carried over from ProdCom Listener (same variable names, so
|
|
module CSS written against these looks native everywhere). Modules must
|
|
style with these variables only — no hardcoded colors. */
|
|
|
|
:root {
|
|
--bg: #0b0e12;
|
|
--panel: #131920;
|
|
--border: #232b35;
|
|
--text: #e6edf3;
|
|
--muted: #8b98a5;
|
|
--accent: #2ee59a;
|
|
--danger: #f4433c;
|
|
--warn: #f0b429;
|
|
/* grid geometry (shell.js reads these) */
|
|
--cell-h: 72px;
|
|
--gap: 0px; /* tiles sit flush; their 1px borders are the only seam */
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
html, body { height: 100%; }
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
button { font-family: inherit; }
|
|
|
|
/* ── topbar ────────────────────────────────────────────────────────── */
|
|
|
|
#topbar {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
height: 46px;
|
|
padding: 0 12px;
|
|
background: var(--panel);
|
|
border-bottom: 1px solid var(--border);
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
transition: height 0.22s ease, border-bottom-width 0.22s ease;
|
|
}
|
|
/* collapsed by its notch (and automatically in fullscreen) */
|
|
body.menu-hidden #topbar {
|
|
height: 0;
|
|
border-bottom-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* The bar's show/hide handle: a small accent lip on its bottom edge, the
|
|
same idea as a tile's notch. It sits at the left — never screen-centre,
|
|
where a tile's own (centred) tab lands once both bars are hidden — and
|
|
grows under the pointer so it is easy to hit without being loud at rest.
|
|
It used to be the ProdDash wordmark on a pill hanging past the bar, which
|
|
read as a step cut out of the bar, and animating its font-size on the way
|
|
out was the jerkiest thing on the page in Safari. Everything here moves by
|
|
transform, opacity and its own fixed size: no text metrics, no reflow. */
|
|
#topbar-notch {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 10px;
|
|
width: 28px;
|
|
height: 5px;
|
|
padding: 0;
|
|
border: none;
|
|
border-radius: 0 0 5px 5px;
|
|
background: var(--accent);
|
|
opacity: 0.5;
|
|
cursor: pointer;
|
|
z-index: 60;
|
|
/* Glued to the bar's bottom edge: --topbar-h is the bar's live height, so
|
|
the lip rides the bar's own collapse instead of running a second,
|
|
competing animation against it. */
|
|
transform: translateY(var(--topbar-h, 46px));
|
|
transition: width 0.16s ease, height 0.16s ease,
|
|
opacity 0.16s ease, border-radius 0.16s ease;
|
|
}
|
|
#topbar-notch:hover,
|
|
#topbar-notch:focus-visible {
|
|
width: 60px;
|
|
height: 10px;
|
|
opacity: 1;
|
|
border-radius: 0 0 8px 8px;
|
|
}
|
|
/* With the bar collapsed --topbar-h is 0, so the lip ends up at the very top
|
|
edge without a rule of its own. */
|
|
|
|
#brand {
|
|
font-weight: 800;
|
|
font-size: 15px;
|
|
letter-spacing: 0.02em;
|
|
margin-right: 6px;
|
|
}
|
|
#brand .accent { color: var(--accent); }
|
|
|
|
#topbar .spacer { flex: 1; }
|
|
|
|
.tb-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
padding: 6px 10px;
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
}
|
|
.tb-btn:hover { border-color: var(--muted); }
|
|
.tb-btn:active { background: var(--border); }
|
|
.tb-btn.icon {
|
|
padding: 6px 8px;
|
|
min-width: 30px;
|
|
justify-content: center;
|
|
font-size: 14px;
|
|
line-height: 1;
|
|
}
|
|
.tb-btn.icon svg { display: block; }
|
|
|
|
/* ── dropdown menus ────────────────────────────────────────────────── */
|
|
|
|
.dropdown { position: relative; }
|
|
|
|
.menu {
|
|
position: absolute;
|
|
top: calc(100% + 6px);
|
|
right: 0;
|
|
z-index: 50;
|
|
min-width: 210px;
|
|
max-height: 70vh;
|
|
overflow-y: auto;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 5px;
|
|
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
|
|
}
|
|
.menu[hidden] { display: none; }
|
|
|
|
.menu-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
width: 100%;
|
|
padding: 8px 10px;
|
|
background: none;
|
|
border: none;
|
|
border-radius: 6px;
|
|
color: var(--text);
|
|
font-size: 13px;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
}
|
|
.menu-item:hover { background: var(--border); }
|
|
.menu-item .sub {
|
|
display: block;
|
|
color: var(--muted);
|
|
font-size: 11px;
|
|
margin-top: 2px;
|
|
white-space: normal;
|
|
}
|
|
.menu-item.danger { color: var(--danger); }
|
|
.menu-note {
|
|
padding: 8px 10px;
|
|
color: var(--muted);
|
|
font-size: 12px;
|
|
white-space: normal;
|
|
}
|
|
.menu-divider { height: 1px; background: var(--border); margin: 5px 4px; }
|
|
.menu-title {
|
|
padding: 6px 10px 3px;
|
|
color: var(--muted);
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
/* ── grid ──────────────────────────────────────────────────────────── */
|
|
|
|
#gridwrap {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
overscroll-behavior: contain;
|
|
}
|
|
|
|
#grid {
|
|
position: relative;
|
|
display: grid;
|
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
|
grid-auto-rows: var(--cell-h);
|
|
gap: var(--gap);
|
|
/* Always leave room to drop/grow a tile below the last row. */
|
|
padding-bottom: calc(3 * (var(--cell-h) + var(--gap)));
|
|
}
|
|
|
|
#empty-state {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding-top: 12vh;
|
|
color: var(--muted);
|
|
font-size: 14px;
|
|
pointer-events: none;
|
|
}
|
|
#empty-state[hidden] { display: none; }
|
|
|
|
/* ── tiles ─────────────────────────────────────────────────────────── */
|
|
|
|
.tile {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.tile.dragging,
|
|
.tile.resizing {
|
|
z-index: 40;
|
|
border-color: var(--muted);
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
|
|
opacity: 0.92;
|
|
}
|
|
|
|
.tile-head {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
height: 32px; /* fixed so the notch below can sit at a known offset */
|
|
padding: 0 8px;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border-bottom: 1px solid var(--border);
|
|
cursor: grab;
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
touch-action: none;
|
|
transition: height 0.22s ease, border-bottom-width 0.22s ease;
|
|
}
|
|
.tile.dragging .tile-head { cursor: grabbing; }
|
|
/* hidden = collapsed, not removed, so the notch toggle animates */
|
|
.tile.head-hidden .tile-head {
|
|
height: 0;
|
|
border-bottom-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.tile-head .dot {
|
|
flex: none;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--muted);
|
|
transition: background 0.3s;
|
|
}
|
|
.tile-head .dot.ok { background: var(--accent); box-shadow: 0 0 5px var(--accent); }
|
|
.tile-head .dot.connecting { background: var(--warn); }
|
|
.tile-head .dot.error { background: var(--danger); }
|
|
|
|
.tile-title {
|
|
flex: 1;
|
|
min-width: 0;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
color: var(--muted);
|
|
letter-spacing: 0.03em;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.tile-btn {
|
|
flex: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 22px;
|
|
height: 22px;
|
|
background: none;
|
|
border: none;
|
|
border-radius: 5px;
|
|
color: var(--muted);
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
}
|
|
.tile-btn:hover { background: var(--border); color: var(--text); }
|
|
.tile-btn.close:hover { color: var(--danger); }
|
|
.tile-btn.active { color: var(--accent); }
|
|
.tile-btn svg { width: 13px; height: 13px; display: block; }
|
|
|
|
/* controls a module adds to its own title bar (moduleApi.header) */
|
|
.tile-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
.tile-btn.module-btn {
|
|
width: auto;
|
|
min-width: 22px;
|
|
padding: 0 5px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
}
|
|
.tile-dd { position: relative; display: inline-flex; }
|
|
|
|
/* a module header menu — fixed-positioned by the shell so the tile's
|
|
overflow:hidden can't clip it */
|
|
.tile-menu {
|
|
position: fixed;
|
|
z-index: 70;
|
|
min-width: 180px;
|
|
max-height: 60vh;
|
|
overflow-y: auto;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 5px;
|
|
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
|
|
}
|
|
.tile-menu[hidden] { display: none; }
|
|
|
|
/* notch on the title bar's bottom edge: click to hide/show the bar
|
|
(same idea as the ProdCom Listener's pull-down menubar handle) */
|
|
.tile-notch {
|
|
position: absolute;
|
|
top: 32px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 46px;
|
|
height: 13px;
|
|
padding: 0;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-top: none;
|
|
border-radius: 0 0 10px 10px;
|
|
cursor: pointer;
|
|
z-index: 6;
|
|
opacity: 0;
|
|
transition: opacity 0.15s ease, top 0.22s ease;
|
|
}
|
|
.tile:hover .tile-notch { opacity: 0.6; }
|
|
.tile .tile-notch:hover { opacity: 1; }
|
|
.tile.head-hidden .tile-notch { top: 0; }
|
|
.tile-notch::after {
|
|
content: "";
|
|
display: block;
|
|
width: 18px;
|
|
height: 3px;
|
|
margin: 4px auto 0;
|
|
border-radius: 999px;
|
|
background: var(--muted);
|
|
}
|
|
.tile-notch:hover::after { background: var(--text); }
|
|
|
|
/* Fullscreen is for watching, not arranging: every title bar folds away, and
|
|
a tile's own hidden/shown preference is left untouched for when the screen
|
|
comes back. The notch still peeks one open, and it folds back once the
|
|
pointer has been off that tile for a while (PEEK_HIDE_MS in shell.js). */
|
|
body.fullscreen .tile:not(.head-peek) .tile-head {
|
|
height: 0;
|
|
border-bottom-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
body.fullscreen .tile:not(.head-peek) .tile-notch { top: 0; }
|
|
body.fullscreen .tile.head-peek .tile-head {
|
|
height: 32px;
|
|
border-bottom-width: 1px;
|
|
}
|
|
body.fullscreen .tile.head-peek .tile-notch { top: 32px; }
|
|
|
|
.tile-body {
|
|
flex: 1;
|
|
position: relative;
|
|
min-height: 0;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.tile-resize,
|
|
.tile-resize-left {
|
|
position: absolute;
|
|
bottom: 0;
|
|
width: 20px;
|
|
height: 20px;
|
|
touch-action: none;
|
|
z-index: 5;
|
|
color: var(--muted);
|
|
opacity: 0; /* corner indicators only show while hovering the tile */
|
|
transition: opacity 0.15s ease;
|
|
}
|
|
.tile-resize { right: 0; cursor: nwse-resize; }
|
|
.tile-resize-left { left: 0; cursor: nesw-resize; }
|
|
.tile:hover .tile-resize,
|
|
.tile:hover .tile-resize-left { opacity: 0.6; }
|
|
.tile .tile-resize:hover,
|
|
.tile .tile-resize-left:hover { opacity: 1; color: var(--text); }
|
|
.tile-resize::after,
|
|
.tile-resize-left::after {
|
|
content: "";
|
|
position: absolute;
|
|
bottom: 4px;
|
|
width: 9px;
|
|
height: 9px;
|
|
border-bottom: 2px solid currentColor;
|
|
}
|
|
.tile-resize::after {
|
|
right: 4px;
|
|
border-right: 2px solid currentColor;
|
|
border-radius: 0 0 3px 0;
|
|
}
|
|
.tile-resize-left::after {
|
|
left: 4px;
|
|
border-left: 2px solid currentColor;
|
|
border-radius: 0 0 0 3px;
|
|
}
|
|
|
|
/* bottom-edge pill: drag to move the tile's bottom edge (height only);
|
|
sits between the two corner handles */
|
|
.tile-resize-bottom {
|
|
position: absolute;
|
|
left: 24px;
|
|
right: 24px;
|
|
bottom: 0;
|
|
height: 10px;
|
|
z-index: 5;
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: center;
|
|
cursor: ns-resize;
|
|
touch-action: none;
|
|
opacity: 0;
|
|
transition: opacity 0.15s ease;
|
|
}
|
|
.tile-resize-bottom::after {
|
|
content: "";
|
|
width: 44px;
|
|
max-width: 60%;
|
|
height: 5px;
|
|
margin-bottom: 2px;
|
|
border-radius: 999px;
|
|
background: var(--muted);
|
|
transition: background 0.15s ease;
|
|
}
|
|
.tile:hover .tile-resize-bottom { opacity: 0.6; }
|
|
.tile .tile-resize-bottom:hover,
|
|
.tile.resizing .tile-resize-bottom { opacity: 1; }
|
|
.tile .tile-resize-bottom:hover::after,
|
|
.tile.resizing .tile-resize-bottom::after { background: var(--text); }
|
|
|
|
/* ── seam divider between adjacent tiles ───────────────────────────── */
|
|
|
|
/* When two tiles share a vertical edge, an invisible strip sits on the seam;
|
|
hovering reveals a small macOS-split-view-style grab pill. Dragging it
|
|
trades width between the two tiles. */
|
|
.tile-divider {
|
|
position: absolute;
|
|
width: 14px;
|
|
transform: translateX(-50%);
|
|
z-index: 30;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: col-resize;
|
|
touch-action: none;
|
|
}
|
|
|
|
.tile-divider::after {
|
|
content: "";
|
|
width: 5px;
|
|
height: 44px;
|
|
max-height: 55%;
|
|
border-radius: 999px;
|
|
background: var(--muted);
|
|
opacity: 0;
|
|
transition: opacity 0.15s ease, background 0.15s ease;
|
|
}
|
|
|
|
.tile-divider:hover::after { opacity: 0.7; }
|
|
.tile-divider.active::after { opacity: 1; background: var(--text); }
|
|
|
|
/* Hide the pills while a tile itself is being moved or corner-resized —
|
|
they're rebuilt for the new geometry on release. */
|
|
#grid.no-dividers .tile-divider { display: none; }
|
|
|
|
/* ── fullscreen mode ───────────────────────────────────────────────── */
|
|
|
|
/* Fullscreen is a viewing mode: the layout auto-fits the screen, so the
|
|
editing affordances (corner handles, seam pills) are parked. */
|
|
body.fullscreen .tile-resize,
|
|
body.fullscreen .tile-resize-left,
|
|
body.fullscreen .tile-resize-bottom,
|
|
body.fullscreen .tile-divider,
|
|
body.fullscreen #admin-link { display: none; }
|
|
body.fullscreen #grid { padding-bottom: 0; }
|
|
#gridwrap.no-scroll { overflow-y: hidden; }
|
|
|
|
/* status message a module can show while degraded (also used by the shell
|
|
when a module fails to load) */
|
|
.tile-msg {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
text-align: center;
|
|
padding: 12px;
|
|
color: var(--muted);
|
|
font-size: 13px;
|
|
background: var(--panel);
|
|
z-index: 4;
|
|
}
|
|
|
|
/* ── per-tile settings popover ─────────────────────────────────────── */
|
|
|
|
.tile-settings {
|
|
position: absolute;
|
|
top: 36px;
|
|
right: 6px;
|
|
z-index: 45;
|
|
min-width: 220px;
|
|
max-width: calc(100% - 12px);
|
|
max-height: calc(100% - 40px);
|
|
overflow-y: auto;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 10px;
|
|
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
|
|
}
|
|
|
|
/* A popover with grouped settings needs room for its columns; it still
|
|
never grows past the tile (max-width above). */
|
|
.tile-settings.has-groups {
|
|
min-width: min(540px, calc(100% - 12px));
|
|
container-type: inline-size;
|
|
}
|
|
.tile-settings .field-group {
|
|
margin: 0 0 10px;
|
|
padding: 8px 10px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
}
|
|
.tile-settings .field-group-title {
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--muted);
|
|
margin-bottom: 8px;
|
|
}
|
|
.tile-settings 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;
|
|
}
|
|
.tile-settings details.field-group > summary::-webkit-details-marker { display: none; }
|
|
.tile-settings 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;
|
|
}
|
|
.tile-settings details.field-group[open] > summary.field-group-title::before { transform: rotate(90deg); }
|
|
.tile-settings details.field-group[open] > summary.field-group-title { margin-bottom: 8px; }
|
|
.tile-settings .field-group .field:last-child { margin-bottom: 0; }
|
|
.tile-settings .field-group-body.is-columns {
|
|
display: grid;
|
|
grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
|
|
gap: 3px 14px;
|
|
}
|
|
.tile-settings .field-group-body.is-columns .field { margin-bottom: 3px; }
|
|
.tile-settings .field-group-body.is-columns .field-group-help { grid-column: 1 / -1; }
|
|
@container (max-width: 420px) {
|
|
.tile-settings .field-group-body.is-columns { grid-template-columns: 1fr 1fr; }
|
|
}
|
|
@container (max-width: 260px) {
|
|
.tile-settings .field-group-body.is-columns { grid-template-columns: 1fr; }
|
|
}
|
|
.tile-settings .field-group-help,
|
|
.tile-settings .field-help {
|
|
display: block;
|
|
font-size: 11px;
|
|
line-height: 1.4;
|
|
color: var(--muted);
|
|
}
|
|
.tile-settings .field-group-help { margin: -2px 0 6px; }
|
|
.tile-settings .field-help { margin-top: 1px; }
|
|
|
|
.field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
margin-bottom: 10px;
|
|
font-size: 12px;
|
|
color: var(--muted);
|
|
}
|
|
.field input[type="text"],
|
|
.field input[type="number"],
|
|
.field input[type="password"],
|
|
.field select {
|
|
padding: 7px 9px;
|
|
font-size: 13px;
|
|
font-family: inherit;
|
|
color: var(--text);
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 7px;
|
|
}
|
|
.field input:focus, .field select:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
}
|
|
.field.check {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
}
|
|
.field.check input { width: 15px; height: 15px; accent-color: var(--accent); }
|
|
|
|
/* color fields: label on the left, swatch on the right */
|
|
.field.color-field {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.field.color-field > span { flex: 1; min-width: 0; }
|
|
.field.color-field input[type="color"] {
|
|
width: 44px;
|
|
height: 26px;
|
|
padding: 2px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
background: var(--bg);
|
|
cursor: pointer;
|
|
}
|
|
.field.color-field input[type="color"]:focus { outline: none; border-color: var(--accent); }
|
|
.field.color-field .field-help { flex-basis: 100%; }
|
|
|
|
/* ── modal dialogs (layout name prompt etc.) ───────────────────────── */
|
|
|
|
#modal-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 90;
|
|
background: rgba(4, 6, 8, 0.65);
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: center;
|
|
padding-top: 18vh;
|
|
}
|
|
#modal-backdrop[hidden] { display: none; }
|
|
|
|
#modal {
|
|
width: min(360px, calc(100vw - 32px));
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 16px;
|
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
|
|
}
|
|
#modal h2 { font-size: 14px; margin-bottom: 12px; }
|
|
#modal .actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.btn {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
padding: 6px 12px;
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
}
|
|
.btn:hover { border-color: var(--muted); }
|
|
.btn.primary {
|
|
background: var(--accent);
|
|
border-color: var(--accent);
|
|
color: #06281a;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* toast */
|
|
#toast {
|
|
position: fixed;
|
|
bottom: 18px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
z-index: 95;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
color: var(--text);
|
|
font-size: 13px;
|
|
border-radius: 999px;
|
|
padding: 8px 16px;
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
|
|
transition: opacity 0.25s ease;
|
|
}
|
|
#toast[hidden] { display: none; }
|
|
#toast.error { border-color: var(--danger); }
|
|
|
|
/* ── scrollbars ────────────────────────────────────────────────────── */
|
|
|
|
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb {
|
|
background: var(--border);
|
|
border-radius: 999px;
|
|
border: 2px solid transparent; /* inset gutter */
|
|
background-clip: padding-box;
|
|
}
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: #364253;
|
|
background-clip: padding-box;
|
|
}
|
|
::-webkit-scrollbar-corner { background: transparent; }
|
|
|
|
/* Firefox (webkit pseudo-elements unsupported there) */
|
|
@supports (-moz-appearance: none) {
|
|
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
|
|
}
|
|
|
|
@media (max-width: 560px) {
|
|
#topbar { padding: 0 8px; gap: 6px; }
|
|
}
|