Files
ProdDash/public/style.css
T
Claude af46499d92 Flush tile grid + macOS-style seam pill for resizing adjacent tiles
Tiles now sit with zero gap (their 1px borders are the only seam) and the
grid runs edge to edge. Wherever two tiles share a vertical edge, an
invisible strip sits on the seam; hovering reveals a small grab pill
(split-view style) and dragging it moves the shared edge, trading width
between the two tiles. The combined span is preserved, both tiles' min
sizes are honored, and a third tile sitting behind part of the seam
blocks the drag instead of being overlapped. Pills hide during tile
drags/resizes and rebuild for the new geometry on release; seam sizes
persist with the layout. Also fixes cellMetrics treating a 0px grid gap
as falsy and falling back to 10px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNYUv9EyrGSFmCnmH7WYKH
2026-08-30 16:16:32 +00:00

471 lines
11 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;
padding: 8px 12px;
background: var(--panel);
border-bottom: 1px solid var(--border);
user-select: none;
-webkit-user-select: none;
}
#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); }
/* ── 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;
padding: 5px 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;
}
.tile.dragging .tile-head { cursor: grabbing; }
.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 svg { width: 13px; height: 13px; display: block; }
.tile-body {
flex: 1;
position: relative;
min-height: 0;
min-width: 0;
overflow: hidden;
}
.tile-resize {
position: absolute;
right: 0;
bottom: 0;
width: 20px;
height: 20px;
cursor: nwse-resize;
touch-action: none;
z-index: 5;
color: var(--muted);
opacity: 0.6;
}
.tile-resize:hover { opacity: 1; color: var(--text); }
.tile-resize::after {
content: "";
position: absolute;
right: 4px;
bottom: 4px;
width: 9px;
height: 9px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
border-radius: 0 0 3px 0;
}
/* ── 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; }
/* 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: 30px;
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);
}
.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); }
/* ── 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: 6px 8px; flex-wrap: wrap; }
}