﻿html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}
/* Sign in, sign out, and the invite and reset pages. Shared rather than scoped: they are
   the same narrow form three times over. */
.signin {
    max-width: 24rem;
    margin: 3rem auto;
}

.signin h1 {
    font-size: 1.5rem;
    margin-bottom: 1rem;
}

.signin label {
    display: block;
    margin-top: 1rem;
    font-weight: 600;
}

.signin input {
    display: block;
    width: 100%;
    margin-top: 0.25rem;
    padding: 0.5rem;
    font-size: 1rem;
    border: 1px solid #ced4da;
    border-radius: 0.25rem;
}

.signin button {
    margin-top: 1.5rem;
    padding: 0.5rem 1.25rem;
    font-size: 1rem;
    border: 0;
    border-radius: 0.25rem;
    background-color: #1b6ec2;
    color: #fff;
    cursor: pointer;
}

/* "Sign in with Google", to Google's branding guidelines rather than to taste.
   Light theme: #FFFFFF background, 1px #747775 border inside, #1F1F1F text at 14/20, and
   12px / 10px / 12px of padding before the logo, after the logo and after the text. Google Sans
   is not a public font, so the stack falls back through Roboto to the system sans, which is what
   every other implementation does.

   Hover is an elevation rather than a colour change, deliberately: the guidelines ask that the
   button's colours and the mark are not altered, and a shadow says "pressable" without touching
   either. */
.gsi-button {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    box-sizing: border-box;
    width: 100%;
    min-height: 40px;
    padding: 0 12px;
    border: 1px solid #747775;
    border-radius: 4px;
    background-color: #fff;
    color: #1f1f1f;
    font-family: "Google Sans", Roboto, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
    font-size: 14px;
    font-weight: 500;
    line-height: 20px;
    text-decoration: none;
    cursor: pointer;
    -webkit-font-smoothing: antialiased;
}

    .gsi-button:hover {
        box-shadow: 0 1px 2px rgba(60, 64, 67, .3), 0 1px 3px 1px rgba(60, 64, 67, .15);
        text-decoration: none;
        color: #1f1f1f;
    }

    .gsi-button:focus-visible {
        outline: 2px solid #1b6ec2;
        outline-offset: 2px;
    }

/* The mark keeps its own 18px box whatever the text does around it: the guidelines are explicit
   that it is not resized or recoloured on its own. */
.gsi-icon {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
}

/* "or", with a rule either side, between the password form and the Google button. */
.signin-or {
    display: flex;
    align-items: center;
    gap: .75rem;
    margin: 1.5rem 0 1rem;
    color: #6b7280;
    font-size: .8rem;
}

    .signin-or::before,
    .signin-or::after {
        content: "";
        flex: 1 1 auto;
        border-top: 1px solid #e5e7eb;
    }

.signin-error {
    padding: 0.6rem 0.75rem;
    border-radius: 0.25rem;
    background-color: #f8d7da;
    color: #842029;
}

.signin .validation-message {
    display: block;
    margin-top: 0.25rem;
    color: #842029;
    font-size: 0.875rem;
}

.signin-help {
    margin-top: 1.5rem;
    font-size: 0.875rem;
}

/* A line of explanation attached to the control above it, rather than a link at the foot of the
   page like .signin-help. Sits close on purpose: it is about the button it follows.

   Not .muted, which these pages use in a few places and which is defined nowhere they can see
   it -- the only rule for it is inside Diagnose.razor.css, and scoped CSS does not leave its own
   component. Every .muted outside Diagnose renders at full strength today. */
.signin-note {
    margin-top: 0.5rem;
    font-size: 0.875rem;
    color: #6b7280;
}

.nav-signed-in {
    margin-top: 1.5rem;
    border-top: 1px solid rgba(255, 255, 255, 0.2);
    padding-top: 0.5rem;
}

.nav-who {
    display: block;
    padding: 0 1rem;
    font-size: 0.8rem;
    color: rgba(255, 255, 255, 0.6);
}

/* The technicians page. */
.techs {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 2.5rem;
}

.techs th,
.techs td {
    text-align: left;
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid #e5e5e5;
    vertical-align: middle;
}

.techs th {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: #6c757d;
}

.techs tr.inactive td {
    color: #9a9a9a;
}

.techs .actions {
    white-space: nowrap;
    text-align: right;
}

.techs .actions button {
    margin-left: 0.5rem;
    padding: 0.25rem 0.6rem;
    font-size: 0.85rem;
    border: 1px solid #ced4da;
    border-radius: 0.25rem;
    background-color: #fff;
    cursor: pointer;
}

.techs .actions button:disabled {
    opacity: 0.5;
    cursor: default;
}

.tag {
    display: inline-block;
    padding: 0.1rem 0.5rem;
    border-radius: 1rem;
    font-size: 0.75rem;
}

.tag-on {
    background-color: #d1e7dd;
    color: #0f5132;
}

.tag-off {
    background-color: #e9ecef;
    color: #495057;
}

.tag-wait {
    background-color: #fff3cd;
    color: #664d03;
}

.add-tech {
    max-width: 28rem;
}

.add-tech label {
    display: block;
    margin-top: 0.75rem;
    font-weight: 600;
}

.add-tech input,
.add-tech select {
    display: block;
    width: 100%;
    margin-top: 0.25rem;
    padding: 0.4rem;
    font-size: 1rem;
    border: 1px solid #ced4da;
    border-radius: 0.25rem;
}

.add-tech button {
    margin-top: 1.25rem;
    padding: 0.5rem 1.25rem;
    font-size: 1rem;
    border: 0;
    border-radius: 0.25rem;
    background-color: #1b6ec2;
    color: #fff;
    cursor: pointer;
}

.add-tech button:disabled {
    opacity: 0.6;
    cursor: default;
}

.notice {
    padding: 0.6rem 0.75rem;
    border-radius: 0.25rem;
    margin-bottom: 1.25rem;
}

.notice-ok {
    background-color: #d1e7dd;
    color: #0f5132;
}

.notice-error {
    background-color: #f8d7da;
    color: #842029;
}

.techs .actions .edit-link {
    margin-left: 0.5rem;
    font-size: 0.85rem;
}

.cancel-link {
    margin-left: 1rem;
    font-size: 0.9rem;
}

.danger {
    max-width: 34rem;
    margin-top: 1rem;
    padding: 1rem 1.25rem;
    border: 1px solid #f1c2c7;
    border-radius: 0.25rem;
    background-color: #fdf6f7;
}

.danger p {
    margin-bottom: 0.75rem;
}

.danger hr {
    margin: 1.25rem 0;
    border: 0;
    border-top: 1px solid #f1c2c7;
}

.danger button {
    padding: 0.4rem 0.9rem;
    font-size: 0.9rem;
    border: 1px solid #ced4da;
    border-radius: 0.25rem;
    background-color: #fff;
    cursor: pointer;
    margin-right: 0.5rem;
}

.danger button:disabled {
    opacity: 0.5;
    cursor: default;
}

.danger .danger-button {
    border-color: #b02a37;
    background-color: #b02a37;
    color: #fff;
}

/* --------------------------------------------------------------------------
   Waiting indicator: three dots bouncing in order.

   Global rather than scoped to Diagnose, for two reasons. It is a generic "still
   working" widget that any page with a wait will want, and Blazor's CSS isolation
   rewriter has no business anywhere near a @keyframes block: it appends a scope
   attribute to selectors, and keyframe stops are not selectors.

   currentColor throughout, so it takes the colour of whatever it sits inside: the
   state pill in the header, the muted grey on the working line, without either
   being restated here.
   -------------------------------------------------------------------------- */
.dots {
    display: inline-flex;
    align-items: flex-end;
    gap: 0.2em;
    margin-left: 0.35em;
}

.dots span {
    width: 0.32em;
    height: 0.32em;
    border-radius: 50%;
    background: currentColor;
    animation: beacon-dot-bounce 1.05s infinite ease-in-out both;
}

/* In order, not together. The stagger is the whole effect. */
.dots span:nth-child(2) { animation-delay: 0.15s; }
.dots span:nth-child(3) { animation-delay: 0.3s; }

@keyframes beacon-dot-bounce {
    0%, 70%, 100% { transform: translateY(0);        opacity: 0.4; }
    35%           { transform: translateY(-0.45em);  opacity: 1; }
}

/* Respect the system setting. The dots say "still working", and a steady dimmed row
   says the same thing, so there is nothing that needs replacing. */
@media (prefers-reduced-motion: reduce) {
    .dots span {
        animation: none;
        opacity: 0.6;
    }
}

/* ==========================================================================
   The shell: a bar across the top, then menu / content / handle / panel.

   Global rather than scoped to MainLayout, because the script that drives it sets classes on
   <html> and scoped CSS cannot reach those, and because the right panel's visibility is
   decided with :has(), which the CSS isolation rewriter does not survive predictably.
   ========================================================================== */

:root {
    --nav-w: 250px;
    --right-w: 340px;

    /* Beacon Computers' own blue, sampled from the logo. One variable rather than the literal
       in two rules, because the top bar and the nav column have to stay the same colour: they
       meet at a corner, and two values that drift apart show up there as a seam. */
    --brand-blue: #00579C;

    /* The other half of the logo, sampled the same way. Read from here by scoped component
       stylesheets as well as by this file -- CSS isolation rewrites selectors, not custom
       property inheritance, so a variable declared on :root reaches every component. That is
       what keeps the Disconnect button and the session list's attention dot the same orange
       instead of two oranges that look the same until they are beside each other. */
    --brand-orange: #E77817;
    --brand-orange-dark: #C8650F;

    /* A lighter tint of the same orange, for a large filled surface where the full-strength
       logo colour is too heavy. Same hue and same saturation -- #E77817 is hsl(28, 82%, 50%)
       and this is hsl(28, 82%, 60%) -- so it is the logo orange with the lightness turned up,
       not a different orange picked to look similar. */
    --brand-orange-light: #ED9345;
}

/* height, not min-height: the shell fills exactly one viewport and never grows past it, so the
   document itself never scrolls. That is what lets shell-content and shell-right each get their
   own scrollbar below (overflow-y: auto) instead of one long page-level scroll that drags the
   customer/machine panel out of view every time the chat gets long. */
.shell {
    display: flex;
    flex-direction: column;
    height: 100vh;
}

.shell-bar {
    display: flex;
    align-items: center;
    gap: .5rem;
    height: 3rem;
    padding: 0 .75rem;
    background: var(--brand-blue);
    color: #fff;
    position: sticky;
    top: 0;
    z-index: 3;
}

.shell-brand {
    color: #fff;
    font-size: 1.1rem;
    text-decoration: none;
}

.shell-spacer {
    flex: 1;
}

.shell-body {
    display: flex;
    flex: 1;
    min-height: 0;
}

.shell-nav {
    flex: 0 0 var(--nav-w);
    width: var(--nav-w);
    overflow: hidden;
    /* Flat brand blue, not the old navy-to-purple gradient: the purple was not a Beacon
       colour, and a column that changes hue down its length fights the orange it sits beside. */
    background: var(--brand-blue);
    transition: flex-basis .15s ease, width .15s ease;
}

html.nav-collapsed .shell-nav {
    flex-basis: 0;
    width: 0;
}

/* The menu's contents track the column's width rather than filling it.

   Not width: 100%, and not left to reflow. The column animates to zero when it collapses, and
   a percentage would squeeze the links through every width on the way, which reads as the menu
   breaking rather than closing. --nav-w holds still through that -- nav-collapsed sets the
   column's own width, not the variable -- so the contents keep their shape and slide out.

   Here rather than in NavMenu.razor.css, which is where it used to be as a flat 250px. That
   file is scoped, so its rules carry an extra attribute and its <link> comes after this one:
   anything it says about the column's width wins over what the column actually is, and the
   resize handle drags a column whose contents stay whatever that file last said. Geometry the
   handle drives belongs in one place, with the variable it drives. */
.shell-nav .nav-scrollable {
    width: var(--nav-w);
}

/* min-width: 0 is load-bearing on every flex child here. Without it a long unbroken line in a
   transcript, or a wide JSON blob, sets the column's minimum to its content width and pushes
   the panel off screen instead of scrolling inside it. */
.shell-content {
    flex: 1 1 auto;
    min-width: 0;
    /* Its own scrollbar now that .shell is capped to one viewport tall: the middle panel
       scrolls independently of the right panel (shell-right, overflow: auto below) instead of
       both riding one page-level scroll together. */
    overflow-y: auto;
    padding: 1rem 1.5rem 2rem;
}

/* 6px of target, and the cursor is the affordance: col-resize is the left-right arrows. */
.shell-resizer {
    flex: 0 0 6px;
    cursor: col-resize;
    background: #e5e7eb;
}

.shell-resizer:hover,
.shell-resizer:focus-visible {
    background: #93c5fd;
    outline: none;
}

.shell-right {
    position: relative;
    flex: 0 0 var(--right-w);
    width: var(--right-w);
    min-width: 0;
    /* Was already here, but only starts scrolling anything now that .shell is capped to one
       viewport tall above — before that, the panel just grew as long as its content did. */
    overflow: auto;
    border-left: 1px solid #e5e7eb;
    background: #fafafa;
}

.right-body {
    padding: .9rem;
}

.right-close {
    position: absolute;
    top: .4rem;
    right: .4rem;
    color: #6b7280;
}

/* The panel exists only when a page put something in it. :has() asks the DOM that question
   directly, which is what lets the layout stay static: there is no state to hold and nothing
   to tell it. */
.shell:not(:has(.right-body > *)) .shell-resizer-right,
.shell:not(:has(.right-body > *)) .shell-right,
.shell:not(:has(.right-body > *)) .js-right-toggle {
    display: none;
}

html.right-closed .shell-resizer-right,
html.right-closed .shell-right {
    display: none;
}

/* Each handle goes with the column it resizes. A collapsed menu has no width to drag, and a
   handle left behind sits between the content and nothing. */
html.nav-collapsed .shell-resizer-nav {
    display: none;
}

.icon-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: none;
    border-radius: .25rem;
    background: none;
    color: inherit;
    cursor: pointer;
}

.icon-button:hover {
    background: rgba(255, 255, 255, .15);
}

.right-close:hover {
    background: #f3f4f6;
}

.icon-button svg {
    width: 1.15rem;
    height: 1.15rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Set on <body> for the duration of a drag. Without it the cursor reverts the moment the
   pointer leaves the 6px handle, which it does instantly, and the drag selects text across the
   whole page instead of resizing. */
body.resizing {
    cursor: col-resize;
    user-select: none;
}

/* The menu animates its width so collapsing it slides rather than jumps. During a drag that
   same transition makes the column trail the pointer by .15s, which reads as the handle being
   sticky. Off for the duration; the collapse animation is unaffected. */
body.resizing .shell-nav {
    transition: none;
}

/* Narrow screens: the menu overlays rather than taking a column, so collapsing it is the
   difference between a usable page and an unusable one rather than a preference. */
@media (max-width: 800px) {
    .shell-nav {
        position: fixed;
        top: 3rem;
        bottom: 0;
        left: 0;
        z-index: 10;
        flex-basis: 0;
        box-shadow: 2px 0 8px rgba(0, 0, 0, .3);
    }

    html.nav-collapsed .shell-nav {
        width: 0;
        box-shadow: none;
    }

    /* The menu is fixed and floats over the content here, so nothing sits between the two for
       a handle to be the boundary of. */
    .shell-resizer-nav {
        display: none;
    }
}

/* Left-panel search (NavMenu + js/search.js). Global, not NavMenu.razor.css: the result rows
   are created by script, so they never carry Blazor's CSS-isolation attribute. */
.nav-search input { width: 100%; padding: .3rem .5rem; border-radius: .25rem; border: 1px solid #4b5563; }
.nav-search-results { list-style: none; margin: .25rem 0 0; padding: 0; max-height: 50vh; overflow-y: auto; overflow-x: hidden; }
.nav-search-results li a { display: flex; align-items: center; flex-wrap: nowrap; gap: .4rem; padding: .25rem .4rem; color: #fff; text-decoration: none; font-size: .85rem; border-radius: .25rem; }
.nav-search-results li a:hover, .nav-search-results li a:focus { background: rgba(255,255,255,.12); }

/* One row, one line. A wrapped hostname pushes every row below it down and turns a ten-row
   dropdown into a wall, and it wraps at a width the menu may not even be at any more now that
   the menu is resizable: widening it should buy more of the name, not the same two lines with
   slack on the right. So the label takes whatever width is left and ellipsises past it, and
   the state badge keeps its own (flex-shrink: 0, below) at the end of the row.

   min-width: 0 is what makes that work at all. A flex item's floor is its content width by
   default, so without this the longest hostname sets the row's width, the row refuses to
   shrink to the column, and it overflows sideways instead of ellipsising. */
.nav-search-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-search-results .nav-search-empty { color: #cbd5e1; font-size: .8rem; padding: .25rem .4rem; }
.conn-mini { font-size: .65rem; line-height: 1; padding: .15rem .35rem; border-radius: .6rem; color: #fff; flex-shrink: 0; }
.conn-in { background: #2563eb; }
.conn-avail { background: #16a34a; }
.conn-off { background: #6b7280; }

/* Signed-in name + Sign out, in the blue shell bar (MainLayout). */
.bar-user { display: inline-flex; align-items: center; gap: .75rem; font-size: .85rem; color: #fff; margin-right: .5rem; }
.bar-user a { color: #fff; text-decoration: underline; }
.bar-user-name { font-weight: 600; }
.nav-search { margin-top: .5rem; }

/* A secondary action sitting beside a value in a spec sheet -- Reassign next to the customer
   name. Without this it inherits nothing: .edit-link is styled under .techs and .detail-head
   only, so in a definition list the two anchors rendered butted together as one word. */
.detail dd .edit-link {
    margin-left: 0.5rem;
    font-size: 0.85rem;
}

/* Customer and machine detail pages. A spec sheet: labels and values aligned in two columns. */
.detail-head {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}

.detail-head h1 {
    margin-bottom: 0;
}

.detail-head .edit-link {
    font-size: 0.9rem;
}

.detail {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.3rem 1.25rem;
    margin: 1rem 0 1.75rem;
    max-width: 48rem;
}

.detail dt {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: #6c757d;
    align-self: center;
}

.detail dd {
    margin: 0;
    overflow-wrap: anywhere;
}

.add-tech .check-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 600;
}

.add-tech .check-row input {
    display: inline-block;
    width: auto;
    margin: 0;
}

/* Machine page: spec sheet on the left, Machine Notes pane on the right. */
.machine-layout {
    display: grid;
    grid-template-columns: minmax(0, 48rem) minmax(18rem, 28rem);
    gap: 1.5rem;
    align-items: start;
    margin: 1rem 0 1.75rem;
}

.machine-layout .detail {
    margin: 0;
}

@media (max-width: 64rem) {
    .machine-layout {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* Server Settings hub: one link per section page. */
.settings-links {
    list-style: none;
    padding: 0;
}

.settings-links li {
    padding: 0.4rem 0;
}
