Files
usmt-dash/web/layout.css
T
Johan Sandoval d24be4a2d6 Polish sticky headers, Custom View, docs page and dialogs
- Status tables: the whole header (column names + filters) sticks as one
  block, tucked 1px under the group header, so no gap can open between
  the rows; offsets use fractional heights
- Phones/tablets: the filter panel sticks under the group header too, and
  is opaque and compact (three filters per row on phones)
- Custom View: fix the group header being pushed over the column header
  (it lives inside the view's own scroll box); the column header now pins
  to the top of that box on desktop, and the header strip sticks on phones
- All Devices: group title is "All Devices (N)" instead of "Missing
  Software", matching the page title count
- Documentation fits the window on desktop: only the article list and the
  article scroll; placeholder no longer says "from the left"
- Dialogs sit above the terminal dock so it can't cover their last rows;
  page bottom padding follows the dock's measured height
2026-10-06 16:58:05 +00:00

241 lines
12 KiB
CSS

/*
* Layout additions for the dashboard, loaded after the page's own <style> block:
* - sticky status-group headers (all sizes) and sticky column headers (desktop)
* - card view for tables on phones and tablets
* - small-screen fixes for forms, docs and dialogs
* layout.js measures the sticky offsets and labels card cells.
*/
/* ---------------------------------------------------------------------------------------
* Sticky headers
*
* A status group's header ("Not Started (37)") sticks under the top nav while you scroll
* through that group, then hands over to the next group's header. Beneath it the table's
* header (column names + filters on desktop, the filter panel on phones) sticks as one
* block, so the rows inside it can never drift apart. layout.js sets --nav-h and, per
* table, --sticky-top (nav + group header).
* ------------------------------------------------------------------------------------- */
.group-header { position: sticky; top: var(--nav-h, 60px); z-index: 30; }
.master-table:not(#table-custom) thead { position: sticky; top: var(--sticky-top, 112px); z-index: 20; }
.master-table:not(#table-custom) thead tr.filter-row th {
/* the original header tint, but opaque so rows don't show through */
background: linear-gradient(rgba(0,0,0,0.2), rgba(0,0,0,0.2)), var(--bg-card);
}
@media (min-width: 1101px) {
/* An overflowing wrapper would capture the sticky header; these tables never need to
scroll sideways at desktop widths (they're fixed-layout, 100% wide) */
#masterList .table-wrapper, #software .table-wrapper, .stack-box.table-wrapper { overflow: visible !important; }
/* Custom View scrolls inside its own box (it can be wider than the page), so its
group header stays put and its column header sticks to the top of that box */
#wrapper-custom .group-header { position: static; }
#scroll-custom thead th { position: sticky; top: 0; z-index: 20; }
}
/* ---------------------------------------------------------------------------------------
* Bottom terminal dock: keep page content and dialogs clear of it.
* layout.js sets --dock-h to the dock's current height (bar only, or bar + terminal).
* ------------------------------------------------------------------------------------- */
body { padding-bottom: var(--dock-h, 40px) !important; }
/* Dialogs sit above the dock (below the top nav), so it can't cover their bottom rows */
.modal-overlay { z-index: 1950 !important; }
#modal { z-index: 1960 !important; }
#syncModal { z-index: 1970 !important; }
#confirmModal, #promptModal { z-index: 1980 !important; }
/* Documentation fits the window on desktop: only the article list and the article scroll */
@media (min-width: 901px) {
#docs > div { height: calc(100vh - var(--nav-h, 60px) - 50px - var(--dock-h, 40px)) !important; }
}
/* ---------------------------------------------------------------------------------------
* Card view for data tables (phones and tablets)
*
* An 11-column table can't be read on a phone or an iPad in portrait, and sideways
* scrolling only ever shows a few columns. Below 1100px (where the nav also collapses)
* each row becomes a card: the device name as its title and every other column as a
* "label: value" line. mobile.js copies the column names onto the cells. Cards flow one
* per row on phones and two or more on tablets. The column filter row becomes a filter
* panel above the cards.
* ------------------------------------------------------------------------------------- */
@media (max-width: 1100px) {
.table-wrapper { overflow-x: visible !important; }
table.master-table {
display: block; width: 100% !important; min-width: 0 !important; max-width: none !important;
table-layout: auto; font-size: 0.9em;
}
table.master-table colgroup { display: none; }
table.master-table thead { display: block; }
table.master-table thead tr.card-label-row,
table.master-table thead tr.card-aux-row { display: none; }
/* Filters: a wrapping panel of labeled controls */
table.master-table thead tr.filter-row {
display: flex; flex-wrap: wrap; gap: 8px 10px; padding: 10px 12px;
/* opaque: the panel sticks over the cards while scrolling */
background: linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15)), var(--bg-card);
border-bottom: 1px solid var(--border-color); box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
table.master-table thead tr.filter-row th {
display: block; flex: 1 1 150px; min-width: 0; width: auto !important;
padding: 0; border: 0; background: transparent;
}
table.master-table thead tr.filter-row th:empty,
table.master-table thead tr.filter-row th[data-label=""] { display: none; }
table.master-table thead tr.filter-row th::before {
content: attr(data-label); display: block; margin-bottom: 3px;
font-size: 0.78em; font-weight: 600; color: var(--text-muted);
}
/* Custom View: cards don't need the sideways-scrolling box, so it scrolls with the
page like the other tables and its header strip sticks under the group header */
#wrapper-custom, #scroll-custom { overflow: visible !important; max-height: none !important; }
#table-custom thead { position: sticky; top: var(--sticky-top, 112px); z-index: 20; }
/* Custom View keeps its header (sort / filter / remove buttons) as a strip of chips */
#table-custom thead tr.card-label-row {
display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 12px;
background: var(--table-header-bg);
}
#table-custom thead tr.card-label-row th {
display: flex; align-items: center; gap: 4px; width: auto !important; min-width: 0 !important;
padding: 4px 8px; border: 1px solid rgba(255,255,255,0.2); border-radius: 999px;
position: static; white-space: nowrap;
}
/* Rows -> cards */
table.master-table tbody {
display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 330px), 1fr));
gap: 10px; padding: 10px;
}
table.master-table tbody tr {
display: block; position: relative;
background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 8px;
padding: 10px 12px; box-shadow: 0 2px 5px rgba(0,0,0,0.25);
}
/* Label in a fixed left gutter, value flows to its right (so a value made of several
pieces - a name plus its copy icon, a list with links - stays one value) */
table.master-table tbody td {
display: block; position: relative; min-height: 1.3em;
padding: 6px 0 6px 9.2em; border: 0; border-bottom: 1px solid var(--border-light);
width: auto !important; max-width: none !important; height: auto !important;
white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
}
table.master-table tbody td::before {
content: attr(data-label); position: absolute; left: 0; top: 6px; width: 8.6em;
font-size: 0.85em; font-weight: 600; color: var(--text-muted); line-height: 1.35;
}
table.master-table tbody td:last-child { border-bottom: 0; }
/* First column (the device) is the card's title */
table.master-table tbody td:first-child {
display: block; font-size: 1.12em; font-weight: 700; padding: 0 34px 8px 0; min-height: 0;
}
table.master-table tbody td:first-child::before { content: none; }
/* The per-row copy button has no column name: pin it to the card's corner */
table.master-table tbody td[data-label=""]:not(:first-child) {
position: absolute; top: 8px; right: 8px; display: block; padding: 0; border: 0;
}
table.master-table tbody td[data-label=""]::before { content: none; }
/* "No devices" style rows span the full card */
table.master-table tbody td[colspan] { display: block; text-align: center; border: 0; padding: 6px 0; }
/* Filters fold away behind a button (added by mobile.js) */
.card-filter-toggle {
display: inline-flex; align-items: center; gap: 6px; margin: 10px 12px 0;
background: transparent; color: var(--text-main); border: 1px solid var(--border-color);
border-radius: 6px; padding: 6px 12px; font: inherit; font-size: 0.85em; cursor: pointer;
}
.card-filter-toggle .count {
background: #3498db; color: #fff; border-radius: 999px; padding: 0 7px; font-size: 0.85em;
}
table.master-table:not(.show-filters) thead tr.filter-row { display: none; }
table.master-table.show-filters thead tr.filter-row { margin-top: 10px; }
}
@media (min-width: 1101px) {
.card-filter-toggle { display: none; }
}
@media (max-width: 600px) {
/* Compact filter panel on phones: three per row, shorter controls */
table.master-table thead tr.filter-row { gap: 6px 8px; padding: 8px 10px; }
table.master-table thead tr.filter-row th { flex: 1 1 96px; }
table.master-table thead tr.filter-row th::before { font-size: 0.7em; margin-bottom: 2px; }
table.master-table thead tr.filter-row input[type="text"],
table.master-table thead tr.filter-row .select-box { height: 32px; padding: 3px 6px; }
table.master-table thead tr.filter-row .select-box { font-size: 13px; }
}
@media (max-width: 420px) {
table.master-table tbody td { padding-left: 7.8em; }
table.master-table tbody td::before { width: 7.2em; }
}
@media (max-width: 900px) {
.main { padding: 14px; }
.main h1 { font-size: 1.55em; }
/* Page headers with an action button (Workbench, All Devices) wrap instead of squeezing */
.header-row { flex-wrap: wrap; gap: 10px; }
/* Group headers: title on one line, copy buttons wrap underneath */
.group-header { flex-wrap: wrap; gap: 8px; font-size: 1.05em; }
.group-header > div { flex-wrap: wrap; }
.copy-table-btn { padding: 5px 9px; font-size: 0.78em; }
/* Tracked Devices: the two cards stack full width */
.roster-box, .tags-box { min-width: 0 !important; max-width: none !important; flex-basis: 100% !important; }
/* Documentation: article list above the article instead of beside it */
#docs > div { height: auto !important; }
#docs > div > div:last-child { flex-direction: column !important; }
#docs > div > div:last-child > div:first-child {
width: auto !important; min-width: 0 !important; max-height: 38vh;
}
#docs > div > div:last-child > div:last-child { min-height: 60vh; }
#docContent img { max-width: 100%; height: auto; }
#docContent table { display: block; overflow-x: auto; }
#docContent pre { overflow-x: auto; }
#docToolbar { flex-wrap: wrap; gap: 8px; }
/* Modals fill the screen width and scroll inside themselves */
.modal-content {
width: auto !important; max-width: none !important;
margin: 70px 10px 10px !important; /* clear the fixed 60px top nav */
max-height: calc(100vh - 80px) !important;
max-height: calc(100dvh - 80px) !important;
}
.modal-header { padding: 14px 14px 8px; }
.modal-header h2 { font-size: 1.2em; word-break: break-all; }
/* Details title: the Advanced/Simple View button gets its own line under the device
name instead of floating over the content */
#detailsTitle button { float: none !important; display: block; margin: 10px 0 0 !important; font-size: 0.65em !important; }
/* Device details: Advanced View's source / overview / destination columns stack,
and the whole dialog scrolls instead of each column scrolling separately */
#detailsModal .modal-content { height: auto !important; overflow-y: auto !important; display: block !important; }
#detailsBody { display: block !important; padding: 10px !important; clear: both; } /* clear the floated header buttons */
#detailsBody > div[style*="display: flex"] { flex-direction: column !important; padding: 0 !important; gap: 12px !important; }
#detailsBody [style*="overflow-y: auto"] { overflow: visible !important; flex: none !important; }
#detailsBody > div[style*="display: flex"] > div { width: 100% !important; flex: none !important; }
#detailsModal .modal-content td { white-space: normal; word-break: break-word; }
/* Custom View toolbar buttons wrap */
#custom button { flex-shrink: 0; }
#custom > div { flex-wrap: wrap; }
}
@media (max-width: 600px) {
/* iOS zooms into any focused input under 16px; keep fields at 16px on phones */
input[type="text"], textarea, select { font-size: 16px !important; }
.main h1 { font-size: 1.35em; }
.group-header { font-size: 0.98em; padding: 8px 10px; }
.modal-content { margin: 66px 6px 6px !important; max-height: calc(100dvh - 72px) !important; }
}