Files
shopdb-flask/frontend/src/assets/style.css
cproudlock c71183375f
All checks were successful
CI / naming (push) Successful in 3s
CI / frontend (push) Successful in 12s
CI / migrations-mysql (push) Successful in 43s
CI / backend (push) Successful in 4m18s
Fix four things found by looking at the tabbed page
SOFTWARE OPENED COLLAPSED. The installed-software panel was collapsible and
started that way, because it used to be low-priority reference data near the
bottom of one long page. It is now the Software tab's whole reason to exist,
and somebody who clicked that tab has already said what they came for. Made
it neither collapsible nor collapsed.

TRACKED SERVICES VANISHED FROM METRICS. The group is decided by the SERVER, so
a new frontend against a server still serving the old vocabulary puts a panel
in a group the build does not render, and it is fetched, held, and shown
nowhere - with nothing logged. Moving services out of 'actions' is exactly that
shape. The client now falls back to 'details' for any group it does not render,
the same way the server does, so a version-skewed pair degrades to a misplaced
card instead of a missing one.

A TABLE PANEL WITH NOTHING IN IT HID ITSELF. panelVisible honoured `empty` for
list panels and ignored it for tables, for no reason anybody wrote down. A
declared empty message is an answer - "No installed software reported." - where
an absent card is a question. It matters more now that a panel can be the only
thing in a tab, because the tab disappears with it.

THE TAB STRIP HAD A VERTICAL SCROLLBAR. Per the overflow spec, when one axis is
not `visible` the other computes from `visible` to `auto`, so `overflow-x: auto`
alone made the strip scrollable vertically too and the -1px margin the tabs use
to sit on the border was enough to trip it.

Also moved .detail-full into the global stylesheet next to .content-grid, per
frontend/CLAUDE.md: it is detail-page layout, and the other four detail pages
need it when they adopt tabs.

Scope: product
2026-08-31 15:01:14 -04:00

1809 lines
35 KiB
CSS

/* Reset and base styles */
/* Inter is bundled locally via @fontsource-variable/inter (imported in main.js) */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* GE Aerospace Color Palette */
:root {
/* Brand Colors */
--ge-atmosphere-blue: #00003d;
--ge-sky-blue: #4181ff;
--ge-avionics-green: #0ad64f;
--ge-tungsten: #eaeaea;
/* Light Mode (default) */
--primary: #4181ff;
--primary-dark: #2d6ce0;
--secondary: #6c757d;
--success: #0ad64f;
--warning: #ff9500;
--danger: #dc3545;
--info: #4181ff;
--bg: #f5f5f5;
--bg-card: #ffffff;
--bg-card-solid: #ffffff;
--text: #1a1a2e;
--text-light: #6c757d;
--border: #dee2e6;
--link: #4181ff;
--sidebar-bg: #00003d;
--sidebar-text: #ffffff;
--sidebar-width: 250px;
/* Vertical chrome around .main-content. A full-height page subtracts these;
hardcoding a different number is what put a scrollbar on the map. */
--main-pad-top: 20px;
--main-pad-bottom: 70px;
/* Hover variants */
--secondary-dark: #82503f;
--success-dark: #019e4c;
--info-dark: #039ce0;
--warning-dark: #e67c02;
--danger-dark: #e62c51;
/* Additional semantic colors */
--purple: #9c27b0;
--bg-popover: #111111;
}
/* Dark Mode - via data-theme attribute or system preference fallback */
[data-theme="dark"],
:root:not([data-theme]) {
@media (prefers-color-scheme: dark) {
--primary: #4181ff;
--primary-dark: #2d6ce0;
--secondary: #6c757d;
--success: #0ad64f;
--warning: #ff9500;
--danger: #f5365c;
--info: #4181ff;
--bg: #0a0a1a;
--bg-card: rgba(0, 0, 61, 0.4);
--bg-card-solid: #0d0d2b;
--text: rgba(255, 255, 255, 0.9);
--text-light: rgba(255, 255, 255, 0.6);
--border: rgba(65, 129, 255, 0.2);
--link: #4181ff;
--sidebar-bg: #00003d;
--sidebar-text: #ffffff;
}
}
/* Dark Mode - explicit selection */
[data-theme="dark"] {
--primary: #4181ff;
--primary-dark: #2d6ce0;
--secondary: #6c757d;
--success: #0ad64f;
--warning: #ff9500;
--danger: #f5365c;
--info: #4181ff;
--bg: #0a0a1a;
--bg-card: rgba(0, 0, 61, 0.4);
--bg-card-solid: #0d0d2b;
--text: rgba(255, 255, 255, 0.9);
--text-light: rgba(255, 255, 255, 0.6);
--border: rgba(65, 129, 255, 0.2);
--link: #4181ff;
--sidebar-bg: #00003d;
--sidebar-text: #ffffff;
}
/* Card border only in light mode */
[data-theme="light"] .card {
border: 1px solid var(--border);
}
body {
font-family: 'Inter Variable', system-ui, -apple-system, 'Segoe UI', sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
font-size: 15px;
letter-spacing: 0.5px;
}
/* Links */
a {
color: var(--link);
text-decoration: none;
outline: none;
}
a:hover {
color: var(--primary-dark);
text-decoration: none;
}
/* Inline text links get underline on hover (exclude buttons) */
p a:not(.btn):hover,
td a:not(.btn):hover,
.info-value a:not(.btn):hover,
.notes-text a:not(.btn):hover {
text-decoration: underline;
}
/* Headings */
h1, h2, h3, h4, h5, h6 {
color: var(--text);
}
/* Layout */
.app-layout {
display: flex;
min-height: 100vh;
}
.sidebar {
width: var(--sidebar-width);
background: var(--sidebar-bg);
color: var(--sidebar-text);
padding: 0;
position: fixed;
height: 100vh;
overflow-y: auto;
overflow-x: hidden;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
z-index: 1000;
}
.sidebar-header {
padding: 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
text-align: center;
}
.sidebar-header h1 {
font-size: 14px;
font-weight: 400;
text-transform: uppercase;
color: var(--sidebar-text);
margin: 0.5rem 0 0 0;
letter-spacing: 1px;
}
.sidebar-logo {
width: 180px;
height: auto;
display: block;
margin: 0 auto;
filter: brightness(0) invert(1);
}
.sidebar-search {
padding: 10px 15px;
}
.sidebar-search input {
width: 100%;
padding: 0.375rem 0.75rem;
border: 0;
border-radius: 0.25rem;
background: rgba(255, 255, 255, 0.2);
color: white;
font-size: 15px;
height: 34px;
}
.sidebar-search input::placeholder {
color: #fff !important;
font-size: 13px;
opacity: 0.5 !important;
}
.sidebar-search input:focus {
outline: none;
background: rgba(0,0,0,0.2);
box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.45);
}
.sidebar-nav a {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.7rem 1.25rem;
color: rgba(255,255,255,0.85);
text-decoration: none;
transition: all 0.2s;
font-size: 14px;
}
.sidebar-nav a svg {
margin-right: 0.5rem;
flex-shrink: 0;
}
.sidebar-nav a:hover,
.sidebar-nav a.active {
background: rgba(255,255,255,0.1);
color: white;
}
.sidebar-nav a.active {
border-left: 3px solid var(--primary);
}
.sidebar-nav a.external-link::after {
content: '\2197';
font-size: 10px;
margin-left: auto;
opacity: 0.6;
}
.nav-section {
padding: 0.75rem 1.25rem 0.5rem;
color: rgba(255,255,255,0.5);
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 0.5rem;
}
.main-content {
flex: 1;
margin-left: var(--sidebar-width);
padding: var(--main-pad-top) 10px var(--main-pad-bottom) 10px;
overflow-x: hidden;
}
/* Header */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.page-header h1 {
font-size: 20px;
font-weight: 500;
line-height: 20px;
margin: 0;
}
.page-header h2 {
font-size: 20px;
font-weight: 500;
line-height: 20px;
}
/* Cards */
.card {
background: var(--bg-card);
border-radius: 0.25rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
padding: 1.25rem;
margin-bottom: 25px;
border: 1px solid var(--border);
}
[data-theme="dark"] .card {
border: none;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0;
padding: 0.75rem 1.25rem;
background: transparent;
border-bottom: 1px solid var(--border);
font-weight: 600;
font-size: 14px;
color: var(--text);
}
.card-header h3 {
font-size: 16px;
font-weight: 600;
margin: 0;
}
.card-title {
margin-bottom: 0.75rem;
font-weight: 600;
font-size: 16px;
color: var(--text);
}
.card-footer {
padding: 0.75rem 1.25rem;
background-color: transparent;
border-top: 1px solid var(--border);
}
/* Dashboard widgets */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 25px;
margin-bottom: 25px;
}
.stat-card {
background: var(--bg-card);
border-radius: 0.25rem;
padding: 1.25rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.stat-card .label {
font-size: 14px;
color: var(--text-light);
margin-bottom: 0.5rem;
}
.stat-card .value {
font-size: 2rem;
font-weight: 600;
color: var(--text);
}
.stat-card.success .value { color: var(--success); }
.stat-card.warning .value { color: var(--warning); }
.stat-card.danger .value { color: var(--danger); }
.stat-card.info .value { color: var(--info); }
/* Tables */
.table-container {
overflow-x: auto;
white-space: nowrap;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
color: var(--text);
}
tr {
border-bottom: none;
}
th, td {
padding: 0.75rem;
text-align: left;
font-size: 13px;
vertical-align: middle;
white-space: nowrap;
border-top: 1px solid var(--border);
/* line up digits in columns (Inter supports tabular figures) */
font-variant-numeric: tabular-nums;
}
/* Cap a long free-text column (e.g. Description) so it truncates with an
ellipsis instead of widening the row and pushing the Actions column out of
view. Pair with a title attribute to show the full text on hover. */
td.cell-truncate {
max-width: 32rem;
overflow: hidden;
text-overflow: ellipsis;
}
th {
font-weight: 600;
font-size: 11px;
color: var(--text);
text-transform: uppercase;
letter-spacing: 1px;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
border-top: none;
}
th.sortable {
cursor: pointer;
user-select: none;
}
th.sortable:hover {
color: var(--primary);
}
.sort-arrow {
margin-left: 0.25rem;
font-size: 10px;
}
tr:hover {
background: var(--primary);
background: rgba(65, 129, 255, 0.1);
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 9px 19px;
border: none;
border-radius: 0.25rem;
font-size: 11px;
font-weight: 500;
letter-spacing: 1px;
text-transform: uppercase;
cursor: pointer;
transition: all 0.2s;
vertical-align: middle;
line-height: 1;
text-decoration: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.btn:focus {
box-shadow: none;
outline: none;
}
.btn-primary {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.btn-primary:hover {
background: var(--primary-dark);
border-color: var(--primary-dark);
color: white;
}
.btn-secondary {
background: var(--secondary);
color: white;
border-color: var(--secondary);
}
.btn-secondary:hover {
background: var(--secondary-dark);
border-color: var(--secondary-dark);
color: white;
}
.btn-success {
background: var(--success);
color: white;
border-color: var(--success);
}
.btn-success:hover {
background: var(--success-dark);
border-color: var(--success-dark);
}
.btn-info {
background: var(--info);
color: white;
border-color: var(--info);
}
.btn-info:hover {
background: var(--info-dark);
border-color: var(--info-dark);
}
.btn-warning {
background: var(--warning);
color: white;
border-color: var(--warning);
}
.btn-warning:hover {
background: var(--warning-dark);
border-color: var(--warning-dark);
}
.btn-danger {
background: var(--danger);
color: white;
border-color: var(--danger);
}
.btn-danger:hover {
background: var(--danger-dark);
border-color: var(--danger-dark);
}
.btn-link {
color: var(--info);
background: transparent;
border: none;
box-shadow: none;
}
.btn-sm {
font-size: 10px;
font-weight: 500;
padding: 6px 15px;
}
.btn-lg {
padding: 12px 38px;
font-size: 14px;
}
/* Forms */
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
font-size: 14px;
font-weight: 500;
margin-bottom: 0.5rem;
color: var(--text);
}
.form-control {
width: 100%;
padding: 0.625rem 0.875rem;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.9375rem;
background: var(--bg-card);
color: var(--text);
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.form-control:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.15);
}
.form-control::placeholder {
color: var(--text-light);
opacity: 0.7;
}
select.form-control {
background: var(--bg-card);
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
padding-right: 2.25rem;
}
select.form-control option {
background: var(--bg-card);
color: var(--text);
}
/* Textarea styling */
textarea.form-control {
min-height: 100px;
resize: vertical;
line-height: 1.5;
}
/* Input group for search with icon */
.input-group {
display: flex;
position: relative;
}
.input-group .form-control {
flex: 1;
}
.input-group-icon {
position: absolute;
left: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: var(--text-light);
pointer-events: none;
}
.input-group .form-control.has-icon {
padding-left: 2.5rem;
}
/* Form labels */
.form-group label {
display: block;
margin-bottom: 0.375rem;
font-weight: 500;
color: var(--text);
font-size: 0.875rem;
}
/* Checkbox and radio styling */
input[type="checkbox"],
input[type="radio"] {
width: 1.125rem;
height: 1.125rem;
accent-color: var(--primary);
cursor: pointer;
}
/* Dark mode form adjustments. Scoped to the explicit theme attribute (the
theme store always stamps it at startup) - a bare prefers-color-scheme
query here leaks dark widget styles into light mode on dark-OS machines. */
[data-theme="dark"] .form-control {
/* background-COLOR, not the shorthand: the shorthand resets a select's
background-repeat/position and the dropdown arrow tiles across the box. */
background-color: var(--bg);
border-color: var(--border);
}
[data-theme="dark"] .form-control:focus {
background-color: var(--bg);
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2);
}
[data-theme="dark"] select.form-control {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
background-color: var(--bg);
background-repeat: no-repeat;
background-position: right 0.75rem center;
}
[data-theme="dark"] select.form-control option {
background: var(--bg-card-solid);
}
/* Form grid */
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
}
/* Badges */
.badge {
display: inline-block;
padding: 0.25em 0.5em;
border-radius: 0.25rem;
font-size: 11px;
font-weight: 500;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
}
.badge-success { background: var(--success); color: #fff; }
.badge-warning { background: var(--warning); color: #fff; }
.badge-danger { background: var(--danger); color: #fff; }
.badge-info { background: var(--info); color: #fff; }
.badge-primary { background: var(--primary); color: #fff; }
.badge-secondary { background: var(--secondary); color: #fff; }
/* Login page */
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg);
}
.login-box {
background: var(--bg-card);
padding: 2rem;
border-radius: 0.25rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
}
.login-logo {
display: block;
width: 160px;
height: auto;
margin: 0 auto 1rem;
}
/* Logo is white by default - invert for light mode */
[data-theme="light"] .login-logo {
filter: invert(1);
}
.login-box h1 {
text-align: center;
margin-bottom: 1.5rem;
font-size: 24px;
color: var(--text);
}
.login-box .btn {
width: 100%;
justify-content: center;
padding: 12px;
}
.error-message {
background: rgba(245, 54, 92, 0.2);
color: var(--danger);
padding: 0.75rem 1rem;
border-radius: 0.25rem;
margin-bottom: 1rem;
font-size: 14px;
border: 1px solid rgba(245, 54, 92, 0.3);
}
/* Modal */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1050;
}
.modal {
background: var(--bg-card-solid);
border-radius: 0.25rem;
width: 100%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 16px 38px -12px rgba(0,0,0,.56), 0 4px 25px 0 rgba(0,0,0,.12), 0 8px 10px -5px rgba(0,0,0,.2);
color: var(--text);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border);
background: transparent;
}
.modal-header h3 {
font-size: 16px;
font-weight: 600;
color: var(--text);
margin: 0;
}
.modal-body {
padding: 1.25rem;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
padding: 1rem 1.25rem;
border-top: 1px solid var(--border);
}
/* Search and filters */
.filters {
display: flex;
gap: 0.75rem;
margin-bottom: 1rem;
flex-wrap: wrap;
align-items: center;
}
.filters input[type="text"],
.filters input.form-control {
flex: 1 1 auto;
min-width: 200px;
max-width: 350px;
}
.filters select,
.filters select.form-control {
min-width: 160px;
max-width: 220px;
flex: 0 1 auto;
}
/* Filter bar with background */
.filter-bar {
display: flex;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
margin-bottom: 1rem;
flex-wrap: wrap;
align-items: center;
}
.filter-bar .form-control {
background: var(--bg);
}
/* Pagination */
.pagination-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
padding-top: 0.75rem;
border-top: 1px solid var(--border);
}
.pagination-info {
display: flex;
align-items: center;
}
.perpage-select {
padding: 0.375rem 0.5rem;
border: 1px solid var(--border);
border-radius: 0.25rem;
background: var(--bg-card);
color: var(--text);
font-size: 13px;
cursor: pointer;
}
.pagination {
display: flex;
justify-content: center;
gap: 0.25rem;
margin-top: 0;
}
.pagination button {
padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
background: transparent;
border-radius: 0.25rem;
cursor: pointer;
font-size: 13px;
color: var(--text);
}
.pagination button:hover:not(:disabled):not(.ellipsis) {
background: var(--bg);
border-color: var(--primary);
}
.pagination button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.pagination button.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.pagination button.ellipsis {
border: none;
cursor: default;
padding: 0.5rem 0.25rem;
}
/* Loading */
.loading {
text-align: center;
padding: 2rem;
color: var(--text-light);
font-size: 14px;
}
/* Sidebar footer */
.sidebar-footer {
margin-top: auto;
border-top: 1px solid rgba(255,255,255,0.1);
}
.theme-toggle {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
width: 100%;
padding: 0.75rem 1.25rem;
background: transparent;
border: none;
color: rgba(255,255,255,0.7);
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
}
.theme-toggle:hover {
background: rgba(255,255,255,0.1);
color: white;
}
/* User menu */
.user-menu {
padding: 1rem 1.25rem;
border-top: 1px solid rgba(255,255,255,0.1);
}
.user-menu .username {
font-size: 14px;
margin-bottom: 0.5rem;
color: rgba(255,255,255,0.85);
}
.user-menu .btn {
width: 100%;
justify-content: center;
}
/* Action buttons in tables */
.actions {
display: inline-flex;
gap: 0.25rem;
align-items: center;
vertical-align: middle;
white-space: nowrap;
}
td.actions {
/* A td must stay display:table-cell - the .actions inline-flex above pulls
the cell out of the table's row box, so its bottom border renders ~1px off
from the other cells. Keep it a cell; space multiple buttons with a margin
instead of the flex gap. */
display: table-cell;
vertical-align: middle;
white-space: nowrap;
}
td.actions .btn + .btn {
margin-left: 0.25rem;
}
/* ============================================
DETAIL PAGES (shared styles)
============================================ */
.detail-page {
max-width: 1400px;
margin: 0 auto;
}
.header-actions {
display: flex;
gap: 0.5rem;
}
/* Hero Card */
.hero-card {
display: flex;
gap: 2rem;
background: var(--bg-card);
border-radius: 0.25rem;
padding: 1.5rem;
margin-bottom: 25px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.hero-image {
flex-shrink: 0;
}
.hero-image img {
width: 180px;
height: 180px;
object-fit: contain;
background: var(--bg);
border-radius: 0.25rem;
padding: 1rem;
}
.hero-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 1rem;
}
.hero-title {
display: flex;
align-items: baseline;
gap: 0.75rem;
}
.hero-title h1 {
font-size: 30px;
font-weight: 600;
margin: 0;
color: var(--text);
line-height: 34px;
}
.hero-alias {
font-size: 18px;
color: var(--text-light);
}
.hero-meta {
display: flex;
gap: 0.5rem;
}
.badge-lg {
padding: 0.35rem 0.75rem;
font-size: 12px;
font-weight: 500;
border-radius: 0.25rem;
}
.hero-details {
display: flex;
flex-wrap: wrap;
gap: 2rem;
margin-top: auto;
}
.hero-detail {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.hero-detail-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-light);
}
.hero-detail-value {
font-size: 15px;
font-weight: 500;
color: var(--text);
}
.hero-detail-value.mono {
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
font-size: 14px;
}
/* Content Grid */
/* Balanced two-column card flow. Uses CSS multicol (not a hand-assigned
grid) so cards distribute by height and the columns stay even no matter
how many cards land on either side. display:contents flattens the two
.content-column wrappers so their cards flow directly into the columns,
which keeps the existing markup unchanged. */
.content-grid {
column-count: 2;
column-gap: 25px;
margin-bottom: 25px;
}
.content-column {
display: contents;
}
/* Full-width alternative to .content-grid, for a tab holding one long list
rather than short fact cards. An application inventory or an event feed
split into two balanced columns reads as two unrelated lists, and the eye
has to hunt for where the first one ended. */
.detail-full {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 25px;
}
.detail-full > * {
min-width: 0;
}
/* Section Cards */
.section-card {
background: var(--bg-card);
border-radius: 0.25rem;
padding: 1.25rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
/* multicol needs per-card spacing (column-gap is horizontal only) and
must not split a card across the column break */
margin-bottom: 25px;
break-inside: avoid;
}
.section-title {
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text);
margin: 0 0 1rem 0;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border);
}
/* Settings form controls - shared by the individual settings pages
(branding, printing, email, integrations, identifiers, search, map...). */
.setting-group {
border-top: 1px solid var(--border);
padding-top: 1rem;
}
.setting-group:first-of-type {
border-top: none;
padding-top: 0;
}
.setting-group h3 {
margin: 0 0 0.5rem 0;
font-size: 1rem;
color: var(--text);
}
.setting-description {
color: var(--text-light);
font-size: 0.9rem;
margin: 0 0 1rem 0;
line-height: 1.5;
}
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.setting-row {
margin-bottom: 1rem;
}
.setting-row.full-width {
grid-column: 1 / -1;
}
.setting-row label {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.setting-row label span {
color: var(--text);
font-size: 0.9rem;
}
.setting-row input,
.setting-row select {
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
font-size: 1rem;
max-width: 400px;
}
.setting-row input[type="number"] {
max-width: 120px;
}
.setting-row input:focus,
.setting-row select:focus {
outline: none;
border-color: var(--primary);
}
.input-hint {
color: var(--text-light);
font-size: 0.8rem;
}
.toggle-label {
flex-direction: row !important;
align-items: center;
justify-content: space-between;
max-width: 400px;
}
.toggle-hint {
display: block;
margin-top: -0.5rem;
margin-left: 0;
}
.toggle-btn {
position: relative;
width: 50px;
height: 26px;
border-radius: 13px;
border: none;
background: var(--secondary);
cursor: pointer;
transition: background 0.2s;
}
.toggle-btn.active {
background: var(--success);
}
.toggle-slider {
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
border-radius: 50%;
background: white;
transition: transform 0.2s;
}
.toggle-btn.active .toggle-slider {
transform: translateX(24px);
}
.status-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background: var(--bg);
border-radius: 4px;
font-size: 0.9rem;
color: var(--text-light);
max-width: 400px;
margin-bottom: 1rem;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.status-dot.inactive { background: var(--secondary); }
.status-dot.warning { background: var(--warning); }
.status-dot.pending { background: var(--primary); }
.status-dot.success { background: var(--success); }
.test-btn {
padding: 0.5rem 1rem;
background: var(--primary);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
}
.test-btn:hover:not(:disabled) {
background: var(--primary-dark);
}
.test-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.view-logs-link {
display: inline-block;
margin-top: 0.5rem;
color: var(--link);
text-decoration: none;
font-size: 0.9rem;
}
.view-logs-link:hover {
text-decoration: underline;
}
.identifier-matrix {
width: 100%;
border-collapse: collapse;
}
.identifier-matrix th,
.identifier-matrix td {
padding: 0.6rem 0.75rem;
text-align: center;
border-bottom: 1px solid var(--border);
}
.identifier-matrix th:first-child,
.identifier-matrix td.identifier-name {
text-align: left;
}
.identifier-matrix th {
color: var(--text-light);
font-weight: 600;
font-size: 0.9rem;
}
.identifier-matrix .identifier-name {
color: var(--text);
}
.color-input-row {
display: flex;
align-items: center;
gap: 0.75rem;
}
.color-input-row input[type="color"] {
width: 48px;
height: 34px;
padding: 2px;
cursor: pointer;
}
.color-input-row input[type="text"] {
max-width: 160px;
}
.map-upload-row {
display: flex;
align-items: center;
gap: 0.75rem;
margin-top: 0.4rem;
}
.map-thumb {
height: 40px;
border: 1px solid var(--border);
border-radius: 4px;
background: #fff;
}
.map-thumb-dark { background: #222; }
.settings-success {
margin-top: 1rem;
padding: 0.75rem;
background: var(--success);
color: white;
border-radius: 4px;
}
/* Info List */
.info-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.info-label {
font-size: 14px;
color: var(--text-light);
}
.info-value {
font-size: 14px;
font-weight: 500;
color: var(--text);
text-align: right;
}
.info-value.mono, .mono {
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
font-size: 13px;
}
/* Small email/Teams action buttons for support contacts */
.contact-action {
display: inline-block;
margin-left: 0.35rem;
padding: 0.05rem 0.4rem;
font-size: 12px;
line-height: 1.5;
border: 1px solid var(--border);
border-radius: 4px;
color: var(--link);
background: var(--bg);
text-decoration: none;
}
.contact-action:hover {
border-color: var(--primary);
color: var(--primary);
}
/* Feature Tags */
.feature-tag {
display: inline-block;
padding: 0.25rem 0.5rem;
margin-left: 0.25rem;
font-size: 11px;
border-radius: 0.25rem;
background: rgba(255, 255, 255, 0.1);
color: var(--text-light);
}
.feature-tag.active {
background: rgba(20, 171, 239, 0.2);
color: var(--info);
}
/* Empty State */
.empty-message {
color: var(--text-light);
font-size: 14px;
font-style: italic;
}
/* Device icon (network detail) */
.device-icon {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(20, 171, 239, 0.2);
border-radius: 0.25rem;
color: var(--info);
}
.device-icon svg {
width: 24px;
height: 24px;
}
/* Network */
.network-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.network-item {
padding: 0.75rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 0.25rem;
}
.network-primary {
display: flex;
align-items: center;
gap: 0.5rem;
}
.ip-address {
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
font-size: 14px;
font-weight: 500;
}
.primary-badge {
padding: 0.2rem 0.4rem;
font-size: 10px;
text-transform: uppercase;
background: var(--success);
color: white;
border-radius: 0.25rem;
}
.network-secondary {
margin-top: 0.25rem;
}
.mac-address {
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
font-size: 13px;
color: var(--text-light);
}
/* Notes */
.notes-text {
margin: 0;
font-size: 14px;
line-height: 1.6;
color: var(--text);
}
/* Audit Footer */
.audit-footer {
display: flex;
justify-content: space-between;
padding: 1rem;
margin-top: 25px;
font-size: 13px;
color: var(--text-light);
border-top: 1px solid var(--border);
}
/* Location link */
.location-link {
color: var(--info);
cursor: pointer;
border-bottom: 1px dashed var(--info);
}
.location-link:hover {
color: var(--link);
opacity: 0.8;
}
/* Badge colors for detail pages */
.badge-primary {
background: var(--primary);
color: white;
}
.badge-printer {
background: var(--purple);
color: white;
}
/* Responsive detail pages */
@media (max-width: 768px) {
.hero-card {
flex-direction: column;
align-items: center;
text-align: center;
}
.hero-title {
flex-direction: column;
align-items: center;
gap: 0.25rem;
}
.hero-details {
justify-content: center;
}
.content-grid {
column-count: 1;
}
.audit-footer {
flex-direction: column;
gap: 0.375rem;
text-align: center;
}
}
/* ============================================
FULLCALENDAR DARK THEME OVERRIDES
============================================ */
.fc {
--fc-border-color: var(--border);
--fc-button-bg-color: var(--primary);
--fc-button-border-color: var(--primary);
--fc-button-hover-bg-color: var(--primary-dark);
--fc-button-hover-border-color: var(--primary-dark);
--fc-button-active-bg-color: var(--primary-dark);
--fc-button-active-border-color: var(--primary-dark);
--fc-page-bg-color: transparent;
--fc-neutral-bg-color: rgba(0, 0, 0, 0.2);
--fc-list-event-hover-bg-color: rgba(255, 255, 255, 0.1);
--fc-today-bg-color: rgba(121, 52, 243, 0.15);
}
.fc .fc-button {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 500;
}
.fc .fc-col-header-cell-cushion,
.fc .fc-daygrid-day-number {
color: var(--text);
}
.fc .fc-daygrid-day.fc-day-today {
background: var(--fc-today-bg-color);
}
.fc-theme-standard .fc-scrollgrid {
border-color: var(--border);
}
.fc-theme-standard td, .fc-theme-standard th {
border-color: var(--border);
}
.fc .fc-daygrid-day-top {
flex-direction: row;
}
.fc-event {
font-size: 12px;
border-radius: 0.25rem;
}
.fc .fc-toolbar-title {
font-size: 18px;
font-weight: 500;
}
.fc .fc-more-link {
color: var(--info);
font-size: 11px;
}
.fc .fc-popover {
background: var(--bg-popover);
border: 1px solid var(--border);
border-radius: 0.25rem;
box-shadow: 0 16px 38px -12px rgba(0,0,0,.56), 0 4px 25px 0 rgba(0,0,0,.12), 0 8px 10px -5px rgba(0,0,0,.2);
}
.fc .fc-popover-header {
background: rgba(0, 0, 0, 0.3);
color: var(--text);
padding: 0.5rem 0.75rem;
font-size: 13px;
}
.fc .fc-popover-body {
padding: 0.5rem;
}
.fc .fc-daygrid-more-link {
margin-top: 2px;
}
.fc-more-tooltip {
position: fixed;
background: var(--bg-card-solid);
border: 1px solid var(--border);
border-radius: 0.25rem;
padding: 0.5rem;
z-index: 9999;
min-width: 180px;
max-width: 350px;
box-shadow: 0 16px 38px -12px rgba(0,0,0,.56), 0 4px 25px 0 rgba(0,0,0,.12), 0 8px 10px -5px rgba(0,0,0,.2);
pointer-events: auto;
color: var(--text);
}
.fc-more-tooltip-event {
padding: 0.25rem 0.5rem;
font-size: 12px;
color: var(--text);
border-left: 3px solid;
margin-bottom: 0.25rem;
background: var(--bg);
border-radius: 0 0.25rem 0.25rem 0;
cursor: pointer;
transition: background 0.15s ease;
}
.fc-more-tooltip-event:hover {
background: rgba(65, 129, 255, 0.15);
}
.fc-more-tooltip-event:last-child {
margin-bottom: 0;
}
/* ============================================
LEAFLET DARK THEME OVERRIDES
============================================ */
.leaflet-container {
background: var(--bg-popover);
}
.leaflet-popup-content-wrapper {
background: var(--bg-popover);
color: var(--text);
border-radius: 0.25rem;
box-shadow: 0 16px 38px -12px rgba(0,0,0,.56), 0 4px 25px 0 rgba(0,0,0,.12), 0 8px 10px -5px rgba(0,0,0,.2);
}
.leaflet-popup-tip {
background: var(--bg-popover);
}
.leaflet-popup-content {
margin: 0.75rem 1rem;
font-size: 14px;
}
.leaflet-popup-content a {
color: var(--info);
}
.leaflet-control-zoom a {
background: rgba(0, 0, 0, 0.7) !important;
color: #fff !important;
border: none !important;
}
.leaflet-control-zoom a:hover {
background: rgba(0, 0, 0, 0.9) !important;
}
.leaflet-control-layers {
background: rgba(0, 0, 0, 0.7);
color: var(--text);
border: none;
border-radius: 0.25rem;
}
/* Notification Banner */
.notification-banner {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.notification-item {
flex: 1;
min-width: 250px;
padding: 0.625rem 0.875rem;
border-radius: 6px;
border-left: 4px solid var(--border);
background: var(--bg-card);
}
.notification-item.type-incident {
border-left-color: var(--danger);
background: rgba(245, 54, 92, 0.06);
}
.notification-item.type-change {
border-left-color: var(--warning);
background: rgba(255, 136, 0, 0.06);
}
.notification-item.type-awareness {
border-left-color: var(--success);
background: rgba(4, 185, 98, 0.06);
}
.notification-text {
display: block;
font-size: 0.85rem;
font-weight: 500;
color: var(--text);
}
.notification-meta {
display: flex;
gap: 0.75rem;
margin-top: 0.25rem;
font-size: 0.75rem;
color: var(--text-light);
}
.notification-ticket {
color: var(--link);
text-decoration: none;
font-family: monospace;
}
.notification-ticket:hover {
text-decoration: underline;
}
[data-theme="dark"] .notification-item.type-incident {
background: rgba(245, 54, 92, 0.1);
}
[data-theme="dark"] .notification-item.type-change {
background: rgba(255, 136, 0, 0.1);
}
[data-theme="dark"] .notification-item.type-awareness {
background: rgba(4, 185, 98, 0.1);
}
/* Light mode is now default, dark mode via prefers-color-scheme */
/* "Show inactive" toggle in a type-settings page header */
.show-inactive {
margin-left: auto;
margin-right: 0.75rem;
display: inline-flex;
align-items: center;
gap: 0.35rem;
font-size: 0.9rem;
color: var(--text-light);
cursor: pointer;
}
/* Clickable list rows: the whole row navigates to the item detail; interactive
cells (the actions column, in-row links) stop propagation so they still work
independently. */
.clickable-row { cursor: pointer; }
.clickable-row:hover td { background: var(--bg); }
/* --- Printing from inside the app shell -------------------------------------
*
* Core's label pages are full-screen routes precisely so the shell cannot reach
* the stock. A tool someone works in for ten minutes wants the navigation
* though, so the shell is hidden here instead: the sidebar goes, the content
* column gives back the margin it was holding for it, and the notification
* banner - which is chrome, not content - goes with them.
*
* Anything the page itself must keep off the paper marks itself .no-print. */
@media print {
.sidebar,
.notification-banner,
.no-print {
display: none !important;
}
.app-layout {
display: block !important;
min-height: 0 !important;
}
.main-content {
margin-left: 0 !important;
padding: 0 !important;
overflow: visible !important;
}
}