Files
logstream/web/style.css
cedricandClaude Opus 5.5 9a1b60fa2b Back-to-top button in the status bar
A small arrow button at the right end of the status bar, shown once the
page has scrolled 400 px, scrolls smoothly back to the top (instantly
when the system asks for reduced motion). Living in the fixed status bar,
it never covers a log row or the details panel, on desktop and phones.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 22:13:30 +02:00

675 lines
31 KiB
CSS

/* ---------- Theme: everything goes through these variables ---------- */
:root {
--bg: #f6f7f9;
--panel: #ffffff;
--panel-2: #f0f2f5;
--border: #e2e5ea;
--text: #1a1d23;
--muted: #6b7280;
--accent: #6366f1;
--accent-soft: rgba(99, 102, 241, .12);
--row-hover: #f5f6fa;
--hit: rgba(99, 102, 241, .18);
--shadow: 0 10px 30px rgba(15, 23, 42, .12);
--chip-l: 38%; /* lightness of project-colored text (labels, Docker apps) */
--sev-crit: #be123c;
--sev-err: #dc2626;
--sev-warning: #d97706;
--sev-notice: #2563eb;
--sev-info: #0d9488;
--sev-debug: #9ca3af;
/* Timeline: severity layers, intensity, graduations */
--h-err: #f87171;
--h-warn: #fbbf24;
--h-other: #a5b4fc;
--h-all: #818cf8;
--h-calm: #a5b4fc;
--h-burst: #f59e0b;
--h-anom: #e11d48;
--h-grid: rgba(100, 116, 139, .28);
--h-hover: rgba(99, 102, 241, .10);
/* Filled severity badges; overwritten by the colors of the "error" / "warning" tags */
--tag-error: #fca5a5;
--tag-error-text: #111827;
--tag-warning: #fdba74;
--tag-warning-text: #111827;
--log-size: 12.5px; /* Settings > Interface > Font size */
/* --log-font is set by Settings > Interface > Font (defaults to --mono) */
--mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;
--sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--radius: 10px;
color-scheme: light;
}
:root[data-theme="dark"] {
--bg: #0f1115;
--panel: #161a20;
--panel-2: #1d222a;
--border: #2a3039;
--text: #e6e8ec;
--muted: #8b93a1;
--accent: #818cf8;
--accent-soft: rgba(129, 140, 248, .16);
--row-hover: #1a1f26;
--hit: rgba(129, 140, 248, .28);
--shadow: 0 10px 30px rgba(0, 0, 0, .5);
--chip-l: 70%;
--sev-crit: #fb7185;
--sev-err: #f87171;
--sev-warning: #fbbf24;
--sev-notice: #60a5fa;
--sev-info: #2dd4bf;
--sev-debug: #6b7280;
--h-err: #ef4444;
--h-warn: #f59e0b;
--h-other: #4f5bd5;
--h-all: #6d74e8;
--h-calm: #4f5bd5;
--h-burst: #fbbf24;
--h-anom: #fb7185;
--h-grid: rgba(148, 163, 184, .2);
--h-hover: rgba(129, 140, 248, .14);
color-scheme: dark;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #0f1115;
--panel: #161a20;
--panel-2: #1d222a;
--border: #2a3039;
--text: #e6e8ec;
--muted: #8b93a1;
--accent: #818cf8;
--accent-soft: rgba(129, 140, 248, .16);
--row-hover: #1a1f26;
--hit: rgba(129, 140, 248, .28);
--shadow: 0 10px 30px rgba(0, 0, 0, .5);
--chip-l: 70%;
--sev-crit: #fb7185;
--sev-err: #f87171;
--sev-warning: #fbbf24;
--sev-notice: #60a5fa;
--sev-info: #2dd4bf;
--sev-debug: #6b7280;
--h-err: #ef4444;
--h-warn: #f59e0b;
--h-other: #4f5bd5;
--h-all: #6d74e8;
--h-calm: #4f5bd5;
--h-burst: #fbbf24;
--h-anom: #fb7185;
--h-grid: rgba(148, 163, 184, .2);
--h-hover: rgba(129, 140, 248, .14);
color-scheme: dark;
}
}
/* ---------- Base ---------- */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font: 14px/1.45 var(--sans);
padding-bottom: 40px;
}
button, input, select { font: inherit; color: inherit; }
button { cursor: pointer; }
svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.muted { color: var(--muted); }
.small { font-size: 12.5px; }
[hidden] { display: none !important; }
/* ---------- Top bar ---------- */
.topbar {
position: sticky; top: 0; z-index: 20;
display: flex; align-items: center; gap: 16px;
padding: 10px 20px;
background: color-mix(in srgb, var(--panel) 88%, transparent);
backdrop-filter: saturate(160%) blur(10px);
-webkit-backdrop-filter: saturate(160%) blur(10px);
border-bottom: 1px solid var(--border);
}
.brand { display: flex; align-items: center; gap: 10px; font-weight: 650; font-size: 16px; letter-spacing: -.01em; }
.brand svg { width: 26px; height: 26px; }
.brand svg rect { fill: var(--accent); stroke: none; }
.brand svg path { stroke: #fff; stroke-width: 3; }
.search {
flex: 1; max-width: 720px;
display: flex; align-items: center; gap: 8px;
padding: 0 6px 0 12px; height: 38px;
background: var(--panel-2);
border: 1px solid transparent;
border-radius: var(--radius);
transition: border-color .15s, box-shadow .15s;
}
.search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.search .ico { width: 17px; height: 17px; color: var(--muted); flex: none; }
.search input {
flex: 1; min-width: 0; height: 100%;
border: 0; outline: 0; background: transparent;
font-family: var(--mono); font-size: 13px;
}
.search input::-webkit-search-cancel-button { cursor: pointer; }
.chip {
flex: none;
border: 1px solid var(--border); background: var(--panel);
border-radius: 7px; padding: 3px 9px; font-size: 12px; font-weight: 600;
color: var(--muted);
}
.chip.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.actions { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.icon-btn {
width: 36px; height: 36px; display: grid; place-items: center;
border: 1px solid transparent; background: transparent; border-radius: 9px; color: var(--muted);
}
.icon-btn:hover { background: var(--panel-2); color: var(--text); }
.icon-btn svg { width: 19px; height: 19px; }
.icon-btn.danger:hover { color: var(--sev-err); }
/* Sun or moon depending on the effective theme */
#themeBtn .moon { display: none; }
:root[data-theme="dark"] #themeBtn .sun { display: none; }
:root[data-theme="dark"] #themeBtn .moon { display: block; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) #themeBtn .sun { display: none; }
:root:not([data-theme="light"]) #themeBtn .moon { display: block; }
}
.btn {
display: inline-flex; align-items: center; gap: 7px;
height: 34px; padding: 0 12px;
border: 1px solid var(--border); border-radius: 9px;
background: var(--panel); font-weight: 550; font-size: 13px;
}
.btn:hover { background: var(--panel-2); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.primary:hover { filter: brightness(1.08); }
.btn.ghost { border-color: transparent; background: transparent; color: var(--muted); }
.btn.ghost:hover { color: var(--text); background: var(--panel-2); }
.live .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.live.on { color: var(--sev-info); border-color: color-mix(in srgb, var(--sev-info) 45%, var(--border)); }
.live.on .dot { background: var(--sev-info); animation: pulse 1.6s infinite; }
.live:disabled { opacity: .5; cursor: not-allowed; }
@keyframes pulse {
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--sev-info) 60%, transparent); }
70% { box-shadow: 0 0 0 7px transparent; }
100% { box-shadow: 0 0 0 0 transparent; }
}
.progress { position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; overflow: hidden; }
body.busy .progress::after {
content: ""; position: absolute; inset: 0; width: 30%;
background: var(--accent);
animation: slide 1s ease-in-out infinite;
}
@keyframes slide { from { transform: translateX(-100%); } to { transform: translateX(400%); } }
/* ---------- Filters ---------- */
.filters {
display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
padding: 12px 20px 8px;
}
.filters select {
height: 32px; padding: 0 28px 0 10px;
border: 1px solid var(--border); border-radius: 8px;
background: var(--panel) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b93a1' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 8px center / 14px;
appearance: none; -webkit-appearance: none;
font-size: 13px; max-width: 220px;
}
.filters select.set { border-color: var(--accent); background-color: var(--accent-soft); }
.spacer { flex: 1; }
#count { font-size: 12.5px; font-variant-numeric: tabular-nums; }
/* Export button and menu */
.export { position: relative; }
.btn.tool { height: 32px; padding: 0 10px; font-size: 13px; }
.btn.tool svg { width: 16px; height: 16px; }
.menu {
position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
width: 290px; padding: 6px;
background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
box-shadow: var(--shadow);
}
.menu button {
display: flex; flex-direction: column; align-items: flex-start; gap: 1px; width: 100%;
padding: 8px 10px; border: 0; border-radius: 8px; background: transparent; text-align: left;
}
.menu button:hover, .menu button:focus-visible { background: var(--panel-2); outline: 0; }
.menu strong { font-size: 13px; font-weight: 600; }
.menu small { font-size: 12px; color: var(--muted); }
.menu-note { margin: 4px 0 0; padding: 8px 10px 4px; border-top: 1px solid var(--border); font-size: 11.5px; color: var(--muted); }
/* Docker logs */
.src-ico { width: 1em; height: 1em; vertical-align: -.15em; margin-right: 4px; color: #2496ed; }
/* Docker app names take the color of their compose project (see hueOf in app.js) */
.row .app.proj { color: hsl(var(--h) 55% var(--chip-l)); }
.row .app.proj .src-ico { color: inherit; }
/* ---------- Timeline (histogram) ---------- */
.histo { position: relative; padding: 0 20px 6px; user-select: none; -webkit-user-select: none; }
.h-plot {
position: relative; height: var(--histo-h, 80px); cursor: crosshair; touch-action: none;
border-bottom: 1px solid var(--border);
}
.h-svg { position: absolute; inset: 0; width: 100%; height: 100%; stroke: none; } /* no icon outline (global svg rule): it would be stretched with the bars */
.h-svg rect { shape-rendering: crispEdges; }
.h-svg .l-err { fill: var(--h-err); }
.h-svg .l-warn { fill: var(--h-warn); }
.h-svg .l-other { fill: var(--h-other); }
.h-svg .l-all { fill: var(--h-all); }
.h-svg .l-calm { fill: var(--h-calm); }
.h-svg .l-burst { fill: var(--h-burst); }
.h-svg .l-anom { fill: var(--h-anom); }
.h-grid i { position: absolute; top: 0; bottom: 0; border-left: 1px dashed var(--h-grid); pointer-events: none; }
.h-grid i.day { border-left-style: solid; }
.h-hover { position: absolute; top: 0; bottom: 0; background: var(--h-hover); pointer-events: none; }
.h-sel { position: absolute; top: 0; bottom: 0; background: var(--accent-soft); box-shadow: inset 1px 0 var(--accent), inset -1px 0 var(--accent); pointer-events: none; }
.h-axis { position: relative; height: 16px; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.h-axis span { position: absolute; top: 2px; transform: translateX(-50%); white-space: nowrap; }
.h-axis span.day { color: var(--text); font-weight: 600; }
.h-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; font-size: 11px; color: var(--muted); }
.h-meta .sp { flex: 1; }
.h-meta .warn { color: var(--sev-warning); cursor: help; }
.h-meta .link-btn { font-size: 11px; }
.h-leg { display: inline-flex; gap: 10px; }
.histo .sw { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 5px; }
.h-tip {
position: absolute; z-index: 30; pointer-events: none; min-width: 170px;
background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: var(--shadow);
padding: 7px 10px; font-size: 12px; font-variant-numeric: tabular-nums;
}
.h-tip .hd { font-weight: 600; margin-bottom: 4px; }
.h-tip .ln { display: flex; justify-content: space-between; gap: 14px; line-height: 1.55; }
/* ---------- Log list ---------- */
.list { margin: 6px 20px 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.list:empty { display: none; }
/* Columns: received, message time, severity, host, app, message. Widths come from
--col-* (set on #table when a column has been resized, see app.js), shared by
the header and every row through subgrid so that the columns line up. */
.table {
display: grid;
grid-template-columns: var(--col-rcv, max-content) var(--col-mt, max-content) var(--col-sev, 4.6rem)
var(--col-host, minmax(5rem, 9rem)) var(--col-app, minmax(4rem, 8rem)) minmax(0, 1fr);
column-gap: 12px;
margin: 6px 20px 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
overflow: clip; /* rounded corners without breaking the sticky header (hidden would) */
}
.table[hidden] { display: none; }
.table .list {
display: grid; grid-template-columns: subgrid; grid-column: 1 / -1;
margin: 0; border: 0; border-radius: 0; background: none; overflow: visible;
}
.table .row { grid-template-columns: subgrid; grid-column: 1 / -1; }
.list-head {
display: grid; grid-template-columns: subgrid; grid-column: 1 / -1;
position: sticky; top: var(--topbar-h, 0px); z-index: 5;
padding: 0 14px 0 11px; border-left: 3px solid transparent;
background: var(--panel-2); border-bottom: 1px solid var(--border);
font-size: 10.5px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
}
.list-head > span { position: relative; min-width: 0; padding: 4px 0; }
/* Labels never widen a column (contain: inline-size), they are cut instead */
.list-head .lbl { display: block; contain: inline-size; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: inherit; }
.list-head .grip { position: absolute; top: 0; bottom: 0; right: -9px; width: 6px; z-index: 1; cursor: col-resize; touch-action: none; }
.list-head .grip::after { content: ""; position: absolute; left: 2px; top: 25%; bottom: 25%; border-left: 1px solid var(--border); }
.list-head .grip:hover::after, .list-head .grip.active::after { top: 0; bottom: 0; border-left: 2px solid var(--accent); left: 2px; }
body.col-resizing, body.col-resizing * { cursor: col-resize !important; user-select: none; -webkit-user-select: none; }
.cols-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin-top: 12px; }
.row {
display: grid;
grid-template-columns: max-content max-content 4.6rem minmax(5rem, 9rem) minmax(4rem, 8rem) 1fr;
gap: 0 12px; align-items: baseline;
padding: 5px 14px 5px 11px;
border-left: 3px solid transparent;
border-bottom: 1px solid var(--border);
font-family: var(--log-font, var(--mono)); font-size: var(--log-size);
font-variant-ligatures: none; /* keep -> != etc. as typed */
cursor: pointer;
}
.row:last-child { border-bottom: 0; }
.row:hover { background: var(--row-hover); }
.row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.row.new { animation: flash 1.2s ease-out; }
@keyframes flash { from { background: var(--accent-soft); } to { background: transparent; } }
.row > * { min-width: 0; }
.row time { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; }
.row time.rcv { color: var(--text); }
.row time .ms { opacity: .6; }
.row .host, .row .app { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .host { font-weight: 600; }
.row .app { color: var(--muted); }
.row .msg { white-space: pre-wrap; word-break: break-word; min-width: 0; }
.sev {
justify-self: start; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
padding: 1px 6px; border-radius: 5px;
color: var(--sev); background: color-mix(in srgb, var(--sev) 14%, transparent);
}
.sev-crit { --sev: var(--tag-error); --sev-text: var(--tag-error-text); }
.sev-err { --sev: var(--tag-error); --sev-text: var(--tag-error-text); }
.sev-warning { --sev: var(--tag-warning); --sev-text: var(--tag-warning-text); }
.sev-notice { --sev: var(--sev-notice); }
.sev-info { --sev: var(--sev-info); }
.sev-debug { --sev: var(--sev-debug); }
.row.sev-crit .sev, .row.sev-err .sev, .row.sev-warning .sev { background: var(--sev); color: var(--sev-text); }
.row.sev-crit .sev { box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--sev-text) 45%, transparent); }
.row.sev-crit, .row.sev-err, .row.sev-warning { border-left-color: var(--sev); }
.row.sev-crit, .row.sev-err { background: color-mix(in srgb, var(--sev) 7%, transparent); }
.row .host[data-act], .row .app[data-act] { cursor: pointer; }
.row .host[data-act]:hover, .row .app[data-act]:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
mark.tag {
padding: 0 4px; border-radius: 4px; font-weight: 650;
box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
mark.hit { background: var(--hit); color: inherit; border-radius: 3px; padding: 0 1px; }
/* Message details */
.details {
grid-column: 1 / -1;
margin: 8px 0 4px; padding: 10px 12px;
background: var(--panel-2); border-radius: 8px;
cursor: auto; font-size: calc(var(--log-size) - .5px);
}
.details dl { display: grid; grid-template-columns: max-content 1fr; gap: 3px 16px; margin: 0; }
.details dt { color: var(--muted); }
.details dd { margin: 0; word-break: break-all; }
.details .acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; font-family: var(--sans); }
.details .acts .btn { height: 28px; font-size: 12px; }
/* ---------- States ---------- */
.empty { text-align: center; padding: 60px 20px; }
.empty svg { width: 40px; height: 40px; color: var(--muted); opacity: .6; }
.empty p { margin: 6px 0; }
.error-banner {
margin: 6px 20px 0; padding: 10px 14px;
border: 1px solid color-mix(in srgb, var(--sev-err) 40%, transparent);
background: color-mix(in srgb, var(--sev-err) 10%, transparent);
color: var(--sev-err); border-radius: var(--radius);
font-family: var(--mono); font-size: 12.5px; white-space: pre-wrap;
}
.pill {
position: fixed; top: 70px; left: 50%; transform: translateX(-50%); z-index: 30;
border: 0; border-radius: 999px; padding: 7px 16px;
background: var(--accent); color: #fff; font-weight: 600; font-size: 13px;
box-shadow: var(--shadow);
}
.status {
position: fixed; left: 0; right: 0; bottom: 0; z-index: 10;
display: flex; gap: 16px; align-items: center;
padding: 6px 20px; font-size: 12px;
background: var(--panel); border-top: 1px solid var(--border);
font-variant-numeric: tabular-nums;
}
.conn { white-space: nowrap; }
.conn::before { content: "●"; margin-right: 6px; color: var(--muted); }
.conn.ok::before { color: var(--sev-info); }
.conn.ko::before { color: var(--sev-err); }
#stats .bad { color: var(--sev-err); }
/* "Back to top", at the right end of the status bar: never over a log row */
.to-top { width: 24px; height: 24px; margin: -5px -6px -5px auto; flex: none; } /* no taller bar */
.to-top svg { width: 16px; height: 16px; }
.to-top[hidden] { display: none; }
.toast {
position: fixed; bottom: 48px; left: 50%; transform: translateX(-50%); z-index: 50;
background: var(--text); color: var(--bg);
padding: 8px 14px; border-radius: 8px; font-size: 13px; box-shadow: var(--shadow);
}
/* ---------- Settings dialog ---------- */
dialog.settings {
width: min(900px, calc(100vw - 32px));
height: min(660px, calc(100vh - 64px)); max-height: none;
}
dialog.settings .dlg { display: flex; flex-direction: column; height: 100%; padding: 0; }
dialog.settings .dlg > header { padding: 14px 16px 14px 22px; border-bottom: 1px solid var(--border); }
.set-body { flex: 1; min-height: 0; display: grid; grid-template-columns: 200px minmax(0, 1fr); }
.set-nav {
display: flex; flex-direction: column; gap: 2px;
padding: 12px 10px; border-right: 1px solid var(--border);
background: color-mix(in srgb, var(--panel-2) 50%, transparent);
}
.set-nav button {
display: flex; align-items: center; gap: 10px;
height: 38px; padding: 0 12px; border: 0; border-radius: 9px;
background: transparent; color: var(--muted); font-weight: 560; font-size: 13.5px; text-align: left;
white-space: nowrap;
}
.set-nav button:hover { background: var(--panel-2); color: var(--text); }
.set-nav button[aria-selected="true"] { background: var(--accent-soft); color: var(--accent); }
.set-nav svg { width: 18px; height: 18px; flex: none; }
.set-panels { overflow-y: auto; padding: 2px 24px 22px; }
.set-panel > .set-section:first-child { margin-top: 16px; }
.hint { margin-top: 8px !important; }
.lbl { font-size: 13px; font-weight: 550; }
.set-panel .preview-list { margin: 14px 0 10px; pointer-events: none; }
.preview-list .row {
cursor: default;
grid-template-columns: max-content max-content minmax(0, max-content) minmax(0, 1fr);
grid-template-areas: "rcv sev host app" "msg msg msg msg";
gap: 3px 10px;
}
.preview-list .row time.rcv { grid-area: rcv; }
.preview-list .row time.mt { display: none; }
.preview-list .row .sev { grid-area: sev; }
.preview-list .row .host { grid-area: host; }
.preview-list .row .app { grid-area: app; display: block; }
.preview-list .row .msg { grid-area: msg; }
dialog {
width: min(720px, calc(100vw - 32px)); max-height: calc(100vh - 64px);
padding: 0; border: 1px solid var(--border); border-radius: 14px;
background: var(--panel); color: var(--text); box-shadow: var(--shadow);
}
dialog::backdrop { background: rgba(10, 12, 16, .45); backdrop-filter: blur(2px); }
.dlg { padding: 18px 20px 16px; }
.dlg header { display: flex; align-items: center; justify-content: space-between; }
.dlg h2 { margin: 0; font-size: 17px; }
.set-section { margin-top: 18px; }
.set-section + .set-section { padding-top: 16px; border-top: 1px solid var(--border); }
.set-section h3 { margin: 0 0 8px; font-size: 13px; font-weight: 650; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.set-section p { margin: 0; }
.field-grid { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 8px 12px; align-items: center; }
.field-grid label { font-size: 13px; font-weight: 550; }
select.field {
height: 34px; padding: 0 30px 0 10px; min-width: 0; width: 100%;
border: 1px solid var(--border); border-radius: 8px;
background: var(--panel) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b93a1' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 9px center / 14px;
appearance: none; -webkit-appearance: none; font-size: 13px;
}
select.field:focus { outline: 0; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.time-preview {
margin: 10px 0 6px !important; padding: 8px 12px; border-radius: 8px;
background: var(--panel-2); font-family: var(--mono); font-size: 12.5px;
}
.set-section.danger h3 { color: var(--sev-err); }
.purge-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 10px; }
.btn.danger { color: var(--sev-err); border-color: color-mix(in srgb, var(--sev-err) 45%, var(--border)); }
.btn.danger:hover:not(:disabled) { background: var(--sev-err); border-color: var(--sev-err); color: #fff; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.purge-status { font-size: 12.5px; }
.purge-status.bad { color: var(--sev-err); }
.purge-status.good { color: var(--sev-info); }
.docker-status { font-size: 13px; margin: 0 0 12px !important; }
.docker-status.good::before, .docker-status.bad::before { content: "●"; margin-right: 6px; }
.docker-status.good::before { color: var(--sev-info); }
.docker-status.bad { color: var(--sev-err); }
.switch-row { display: flex; align-items: center; gap: 10px; font-size: 13px; font-weight: 550; cursor: pointer; }
.dk-opts { display: flex; flex-direction: column; gap: 8px; }
.sl-grid { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 12px 14px; align-items: baseline; margin-top: 14px; }
.sl-protos { display: inline-flex; min-height: 0; padding: 5px; justify-self: start; }
.sl-protos.off { opacity: .5; }
.sl-port { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; }
.sl-port code {
font: 12px var(--mono); padding: 2px 8px; border-radius: 5px;
background: var(--panel-2); border: 1px solid var(--border);
}
.dk-toolbar { display: flex; align-items: center; gap: 12px; margin: 14px 0 6px; }
.dk-filter {
flex: 1; min-width: 0; height: 28px; padding: 0 10px;
border: 1px solid var(--border); border-radius: 7px; background: var(--panel);
font: 12px var(--mono); outline: 0;
}
.dk-filter:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.link-btn { border: 0; background: none; padding: 0; font-size: 12px; font-weight: 550; color: var(--accent); white-space: nowrap; }
.link-btn:hover { text-decoration: underline; }
.dk-legend { font-size: 11.5px; color: var(--muted); margin: 8px 0 12px !important; }
/* Container labels (Settings > Sources) */
.chip-field {
display: flex; flex-wrap: wrap; align-items: center; gap: 5px;
padding: 8px; min-height: 40px;
border: 1px solid var(--border); border-radius: 10px; background: var(--panel);
}
.chip {
display: inline-flex; align-items: center; gap: 4px;
height: 22px; padding: 0 7px;
border: 1px solid var(--border); border-radius: 5px; background: transparent;
color: var(--muted); font: 11px/1 var(--mono); white-space: nowrap; cursor: pointer;
}
.chip:hover { color: var(--text); border-color: var(--muted); }
.chip.on {
color: hsl(var(--h) 55% var(--chip-l));
border-color: hsl(var(--h) 50% var(--chip-l) / .7);
background: hsl(var(--h) 60% 50% / .13);
}
.chip.on:hover { background: hsl(var(--h) 60% 50% / .22); }
.chip.stopped { border-style: dashed; }
.chip.stopped:not(.on) { opacity: .7; }
.chip.locked { opacity: .45; cursor: not-allowed; }
.chip svg { width: 10px; height: 10px; }
.chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.chip-sep { width: 1px; height: 18px; margin: 0 4px; background: var(--border); }
.chip-empty { font-size: 12px; color: var(--muted); padding: 0 4px; }
/* Toggle switch */
input.switch {
appearance: none; -webkit-appearance: none; flex: none;
width: 34px; height: 20px; margin: 0; border-radius: 999px;
background: var(--border); position: relative; cursor: pointer; transition: background .15s;
}
input.switch::after {
content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, .25); transition: transform .15s;
}
input.switch:checked { background: var(--accent); }
input.switch:checked::after { transform: translateX(14px); }
input.switch:disabled { cursor: not-allowed; }
input.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.set-section footer { display: flex; flex-wrap: wrap; gap: 8px; justify-content: space-between; margin-top: 14px; }
.seg { display: inline-flex; padding: 3px; gap: 3px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px; }
.seg button {
border: 0; background: transparent; border-radius: 7px;
height: 30px; padding: 0 14px; font-size: 13px; font-weight: 600; color: var(--muted);
}
.seg button:hover { color: var(--text); }
.seg button[aria-checked="true"] { background: var(--panel); color: var(--accent); box-shadow: 0 1px 3px rgba(0, 0, 0, .12); }
.tag-list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.tag-row {
display: grid; align-items: center; gap: 8px 10px;
grid-template-columns: 38px minmax(8rem, 1fr) 7.5rem auto 36px;
padding: 8px 10px; border: 1px solid var(--border); border-radius: 10px;
background: var(--panel-2);
}
.tag-row.off { opacity: .55; }
.tag-row input[type="color"] {
width: 38px; height: 32px; padding: 0; border: 1px solid var(--border); border-radius: 8px;
background: none; cursor: pointer;
}
.tag-row input[type="color"]::-webkit-color-swatch-wrapper { padding: 3px; }
.tag-row input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 5px; }
.tag-row input[type="text"] {
height: 32px; padding: 0 10px; min-width: 0;
border: 1px solid var(--border); border-radius: 8px; background: var(--panel);
font-family: var(--mono); font-size: 13px; outline: 0;
}
.tag-row input[type="text"]:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.tag-row .preview { font-family: var(--mono); font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; }
.tag-row .opts { display: flex; gap: 4px; }
.opt {
display: inline-flex; align-items: center; height: 28px; padding: 0 8px;
border: 1px solid var(--border); border-radius: 7px; background: var(--panel);
font-size: 11.5px; font-weight: 600; color: var(--muted); cursor: pointer; user-select: none;
}
.opt input { display: none; }
.opt:has(input:checked) { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.tag-row .err { grid-column: 1 / -1; color: var(--sev-err); font-size: 12px; }
.tag-row .err:empty { display: none; }
/* ---------- Mobile ---------- */
@media (max-width: 760px) {
body { font-size: 13.5px; }
.topbar { flex-wrap: wrap; padding: 8px 16px; gap: 8px 10px; }
.search { order: 3; flex-basis: 100%; max-width: none; }
.live .lbl { display: none; }
.filters { padding: 10px 16px 6px; }
.filters select { flex: 1 1 calc(50% - 8px); max-width: none; min-width: 0; }
.spacer { display: none; }
#count { flex: 1 1 50%; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.histo { padding: 0 16px 6px; }
.h-leg { display: none; }
.list, .error-banner, .table { margin-left: 16px; margin-right: 16px; }
/* Phones: no columns, two lines per log (layout below); the widths do not apply */
.table { display: block; }
.table .list { display: block; margin: 0; }
.list-head { display: none; }
.row, .table .row {
grid-template-columns: auto auto 1fr;
grid-template-areas: "rcv sev host" "msg msg msg";
gap: 3px 8px; padding: 8px 12px 8px 10px;
}
.row time.rcv { grid-area: rcv; }
.row time.mt { display: none; } /* still shown in the details panel */
.row .sev { grid-area: sev; }
.row .host { grid-area: host; justify-self: end; max-width: 100%; }
.row .app { display: none; }
.row .msg { grid-area: msg; }
.details { grid-column: 1 / -1; }
.details dl { grid-template-columns: 1fr; }
.details dt { margin-top: 4px; }
.field-grid { grid-template-columns: 1fr; gap: 4px; }
dialog.settings { height: calc(100vh - 24px); width: calc(100vw - 16px); }
.set-body { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); }
.set-nav {
display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); padding: 6px;
border-right: 0; border-bottom: 1px solid var(--border);
}
.set-nav button {
flex-direction: column; justify-content: center; height: auto; padding: 6px 2px; gap: 3px;
font-size: 11.5px; text-align: center;
}
.set-nav button span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
.set-panels { padding: 0 16px 18px; }
#sizeSwitch { display: flex; }
#sizeSwitch button { flex: 1; padding: 0 4px; white-space: nowrap; font-size: 12px; }
.field-grid select { margin-bottom: 6px; }
.status { padding: 6px 16px; }
.tag-row { grid-template-columns: 38px 1fr 36px; }
.tag-row .preview { display: none; }
.tag-row .opts { grid-column: 1 / -1; grid-row: 2; flex-wrap: wrap; }
.tag-row [data-del] { grid-column: 3; grid-row: 1; }
}