/*
 * Components shared by the three pages. Loaded straight after css/tokens.css, before any page's
 * own stylesheet, so a page can still refine what it needs to.
 *
 * Everything here was extracted from a rule that already existed; nothing was invented. Where the
 * two app pages disagreed -- and they disagreed about the padding, radius and type size of almost
 * every component -- the Load Test value wins. That page is tuned for a dense engineering screen
 * and it is the one people keep open all day; the Single page was simply 20% larger by accident of
 * html { font-size: 20px } rather than by decision.
 *
 * What deliberately stayed page-specific: the two headers' inner structure (Load Test carries a
 * logo, a title and a subtitle, Single an h1 -- only the back link both open with is shared),
 * .form-row and .security-fields (same class name, different component on each page), tabs, the
 * parameter tree, the log panel and the modals.
 */

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: var(--font-sans);
    font-size: var(--fs-300);
    /* --lh-ui, not --lh-base: both app pages are dense instrument panels -- toolbar rows, form
       rows, table rows, log rows, stacked dozens deep -- and the extra tenth is paid once per row,
       so across a device table and a log it adds up to whole rows of each. This used to be
       justified by the layout instead: a height:100vh page with an overflow:hidden body and a
       toolbar pinned to one row, which could not afford the space. That layout scrolls and wraps
       now, so the reason is density rather than a ceiling -- the same value, an honest reason.
       Prose-like blocks -- the empty-state paragraph, the protocol-option hint, the landing card
       copy -- ask for --lh-base themselves. */
    line-height: var(--lh-ui);
    color: var(--text-1);
    background: var(--surface-1);
}

/* ---------------------------------------------------------------- icons */

/*
 * One icon: <svg class="icon"><use href="/icons.svg#name"></use></svg>, everywhere an
 * <i class="fas fa-name"> used to be. The symbols live in static/icons.svg and are served from
 * the jar, so they are there in the closed network this emulator is deployed into -- which
 * Font Awesome on cdnjs.cloudflare.com was not.
 *
 * Every declaration below is load-bearing. None of them may be dropped as redundant-looking:
 *
 * width/height 1em. An <svg> that carries a viewBox and no size is a replaced element with no
 * intrinsic size, and CSS resolves that to 300x150px. Font Awesome used to supply the box for
 * free -- its sheet set line-height: 1 on an inline element whose font-size was the button's --
 * and that box is exactly what put an icon-only .btn-sm on the 24px --hit-min the token sheet
 * declares: 6px padding + a 12px --fs-200 glyph + 6px padding. 1em reproduces it from the same
 * font-size, so the button keeps its height and every rule that sizes an icon by setting
 * font-size on it (.log-empty, .no-data, .status-text, .no-devices-icon) keeps working.
 *
 * display: inline-block. Not for descender space: an <svg> is already an inline-level replaced
 * element, so it generates the same atomic inline box either way and inline-block removes nothing
 * from underneath it -- the optical seating on the text is the vertical-align below, and that
 * declaration is the one to reach for when an icon sits too high or too low. What this states is
 * the box model itself, rather than leaving it to the UA sheet's default for <svg>: every rule
 * that sizes an icon by setting font-size on it is sizing that box, and the arithmetic behind the
 * hit target in StaticOfflineAssetsTest reads this line to know it is doing it.
 *
 * flex: none. .btn, .panel-header and .status-text are flex containers, and a flex item may
 * shrink below its width when the row is tight. An icon-only button whose icon shrinks is a hit
 * target that shrinks with it.
 *
 * fill: currentColor. The sprite carries no colours of its own, so an icon takes the colour of
 * its surroundings the way the glyph it replaced did -- including the inline
 * style="color: ..." a few call sites still set.
 *
 * transform-origin: center. Two rules rotate an icon -- .collapse-caret when its panel folds, and
 * .icon-spin below -- and both want the middle. An HTML element's origin is the middle by
 * default; an SVG element's is not reliably so.
 */
.icon {
    width: 1em;
    height: 1em;
    display: inline-block;
    flex: none;
    fill: currentColor;
    vertical-align: -0.125em;
    transform-origin: center;
}

/* The busy spinner, at Font Awesome's own 2s so a request looks no different than it used to. */
.icon-spin {
    animation: icon-spin 2s linear infinite;
}

@keyframes icon-spin {
    to { transform: rotate(360deg); }
}

/* ---------------------------------------------------------------- page chrome */

/*
 * The banner across the top of both app pages. .app-header is the Load Test element and .header
 * the Single one; the two carry different children, but the band itself is the same object and
 * used to be two slightly different bands.
 */
.app-header,
.header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: var(--sp-4) var(--sp-6);
    background: linear-gradient(135deg, var(--brand-800) 0%, var(--brand-700) 100%);
    color: var(--text-inverse);
    box-shadow: var(--shadow-1);
}

.app-header a,
.header a {
    color: var(--text-inverse);
    text-decoration: none;
    font-size: var(--fs-200);
    opacity: 0.9;
}

.app-header a:hover,
.header a:hover {
    opacity: 1;
}

/*
 * The way back to the mode selection, which both app pages open their header with. It is an icon
 * and nothing else, and the rule above sizes a header link's text at --fs-200, so the whole link
 * measured 12px square -- half the --hit-min this project declares for itself, and the smallest
 * target on either page. The padding is on the link rather than the icon so the icon keeps the 1em
 * box every other icon has, and so the box the pointer aims at is the box the focus ring is drawn
 * around.
 */
.app-header-left > a,
.header-left > a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--hit-min);
    min-height: var(--hit-min);
    border-radius: var(--r-sm);
}

/* ---------------------------------------------------------------- panels */

.panel {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-1);
    overflow: hidden;
}

/*
 * outline-offset is negative wherever a focusable control sits flush inside a box that clips.
 *
 * .panel above is overflow: hidden, and a panel header fills its top edge exactly. The shared ring
 * in css/tokens.css is drawn 2px outside the element, which on this one is 2px outside the panel:
 * the top and both sides of it are cut off by the clip and the reader sees a line under the header
 * and nothing else. Drawn inside instead, the whole ring survives. The declaration costs nothing
 * on a header that never takes focus -- only the log panel's, which is a collapse button, does.
 */
.panel-header {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    background: var(--surface-3);
    border-bottom: 1px solid var(--border);
    font-size: var(--fs-300);
    font-weight: 600;
    outline-offset: -2px;
}

.panel-header .icon {
    color: var(--brand-800);
    font-size: var(--fs-300);
}

.panel-body {
    padding: var(--sp-5);
}

/* ---------------------------------------------------------------- buttons */

.btn {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-5);
    border: none;
    border-radius: var(--r-sm);
    /* A <button> does not inherit the page font. Load Test never said so, and its buttons were
       the one part of that screen rendered in the browser's default UI face. */
    font-family: inherit;
    font-size: var(--fs-200);
    font-weight: 500;
    cursor: pointer;
    box-shadow: var(--shadow-1);
    transition: background var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}

.btn:active { transform: translateY(1px); }
.btn:hover:not(:disabled) { box-shadow: var(--shadow-2); }
.btn:disabled { opacity: 0.6; cursor: not-allowed; }

.btn-primary { background: var(--brand-800); color: var(--text-inverse); }
.btn-primary:hover:not(:disabled) { background: var(--brand-700); }
.btn-success { background: var(--success); color: var(--text-inverse); }
.btn-success:hover:not(:disabled) { background: var(--success-hover); }
.btn-danger { background: var(--danger); color: var(--text-inverse); }
.btn-danger:hover:not(:disabled) { background: var(--danger-hover); }
.btn-secondary { background: var(--neutral); color: var(--text-inverse); }
.btn-secondary:hover:not(:disabled) { background: var(--neutral-hover); }
.btn-outline { background: transparent; color: var(--brand-800); border: 1px solid var(--brand-800); }
.btn-outline:hover:not(:disabled) { background: var(--brand-800); color: var(--text-inverse); }

/* Both pages had 6px 12px here. The vertical 6px is kept, and it is what holds an icon-only
   .btn-sm at the --hit-min this sheet declares; the horizontal padding is --sp-4, 10px, which is
   2px narrower than either page had. */
.btn-sm { padding: var(--sp-2) var(--sp-4); }

.btn-group {
    display: flex;
    gap: var(--sp-2);
    flex-wrap: wrap;
}

/* ---------------------------------------------------------------- badges */

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: 2px var(--sp-3);
    border-radius: var(--r-pill);
    background: var(--brand-800);
    color: var(--text-inverse);
    font-size: var(--fs-100);
    font-weight: 600;
    white-space: nowrap;
}

.status-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-5);
    border-radius: var(--r-pill);
    font-size: var(--fs-100);
    font-weight: 600;
    white-space: nowrap;
}

.status-badge::before {
    content: '';
    width: var(--sp-3);
    height: var(--sp-3);
    border-radius: 50%;
}

.status-stopped { background: var(--danger-bg); color: var(--danger-fg); }
.status-stopped::before { background: var(--danger); }
.status-initialized { background: var(--warning-bg); color: var(--warning-fg); }
.status-initialized::before { background: var(--warning); }
.status-running { background: var(--success-bg); color: var(--success-fg); }
.status-running::before { background: var(--success); animation: pulse 1.5s infinite; }

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

/*
 * The transport-security pill. Both pages show one, with variant names that grew apart
 * (sec-psk on Load Test, security-psk on Single) and colours that grew apart with them: PSK was
 * blue on one page and amber on the other, which read as two different states.
 */
.security-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: var(--sp-1) var(--sp-4);
    border-radius: var(--r-pill);
    border: 1px solid transparent;
    font-size: var(--fs-100);
    font-weight: 600;
    white-space: nowrap;
}

.security-badge.sec-none {
    background: var(--warning-bg);
    color: var(--warning-fg);
    border-color: var(--warning);
}

.security-badge.sec-psk,
.security-badge.security-psk {
    background: var(--brand-100);
    color: var(--brand-800);
    border-color: var(--brand-700);
}

.security-badge.sec-x509,
.security-badge.security-x509 {
    background: var(--success-bg);
    color: var(--success-fg);
    border-color: var(--success);
}

/*
 * A device's connection to its own server, read from the connection field of GET /api/devices: a
 * dot and a word, on the Single page's cards and in the Load Test page's Connection column. Not the
 * header's [data-connection-indicator], which is the page's own link to the emulator -- see
 * css/connection.css. The word carries the state; the dot only repeats it in colour.
 */
.device-connection {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    white-space: nowrap;
}

.device-connection::before {
    content: '';
    width: var(--sp-3);
    height: var(--sp-3);
    border-radius: 50%;
    flex-shrink: 0;
}

.device-connection.is-connected::before { background: var(--success); }
.device-connection.is-disconnected::before { background: var(--danger); }
.device-connection.is-unknown::before { background: var(--border-strong); }

/* ---------------------------------------------------------------- tables */

.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-300);
}

.data-table th,
.data-table td {
    padding: var(--sp-4) var(--sp-5);
    text-align: left;
    border-bottom: 1px solid var(--border);
}

.data-table th {
    position: sticky;
    top: 0;
    background: var(--surface-1);
    color: var(--text-2);
    font-size: var(--fs-200);
    font-weight: 600;
}

.data-table tr:hover { background: var(--surface-1); }
.data-table td strong { color: var(--brand-800); }

/* ---------------------------------------------------------------- form fields */

/*
 * One appearance for every text-entry control on every page. This selector outranks .form-row
 * input and .form-group input, which is deliberate: the point is that a field looks the same
 * wherever it sits. Size and spacing are left to the page, which is why they are not set here --
 * adding padding or font-size to this rule would override every page's own field density.
 */
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]),
select,
textarea {
    background: var(--surface-0);
    color: var(--text-1);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    font-family: inherit;
}

/*
 * The :not() chain is repeated rather than shortened: :not() carries the specificity of its
 * argument, so the input branch of the rule above weighs (0,4,1) and a .form-row input:focus at
 * (0,2,1) loses to it. That is exactly what happened on Load Test, which declared a focus border
 * colour that its own "all inputs are white" layer silently outranked, so no text field on that
 * page ever changed colour on focus.
 */
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):hover:not(:disabled),
select:hover:not(:disabled),
textarea:hover:not(:disabled) {
    border-color: var(--border-strong);
}

input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):focus,
select:focus,
textarea:focus {
    border-color: var(--border-focus);
}

input:disabled,
select:disabled,
button:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

/* ---------------------------------------------------------------- scrollbars */

::-webkit-scrollbar {
    width: var(--sp-3);
    height: var(--sp-3);
}

::-webkit-scrollbar-track {
    background: var(--surface-1);
    border-radius: var(--r-sm);
}

::-webkit-scrollbar-thumb {
    background: var(--border-strong);
    border-radius: var(--r-sm);
}

::-webkit-scrollbar-thumb:hover {
    background: var(--text-3);
}
