/* Shell ------------------------------------------------------------------- */

:root {
    --app-gutter: 1.5rem;
    --app-radius: .5rem;
    /* Wide enough for the editor's two columns, narrow enough that a table is not read across a whole monitor. */
    --app-max-width: 1680px;
}

/* No padding on the body: the header spans the viewport and the page gutter lives on .app-main. */
body {
    min-height: 100dvh;
}

.app-header {
    padding-block: .35rem;
}

/* The bar spans the viewport, its contents line up with the page's own column. */
.app-header > .container-fluid {
    max-width: var(--app-max-width);
    padding-inline: var(--app-gutter);
}

.app-header .navbar-brand {
    font-weight: 600;
    letter-spacing: -.01em;
}

.app-logo {
    height: 28px;
    width: auto;
}

/* One logo at a time, chosen by the theme — the wordmark is dark on light and white on dark. */
.app-logo-dark,
[data-bs-theme="dark"] .app-logo-light {
    display: none;
}

[data-bs-theme="dark"] .app-logo-dark {
    display: inline;
}

.app-header .nav-link {
    padding-inline: .75rem;
    border-radius: var(--app-radius);
    color: var(--bs-secondary-color);
}

.app-header .nav-link:hover {
    background: var(--bs-secondary-bg);
    color: var(--bs-body-color);
}

.app-header .nav-link.active {
    background: var(--bs-secondary-bg);
    color: var(--bs-emphasis-color);
    font-weight: 600;
}

.app-main {
    max-width: var(--app-max-width);
    margin-inline: auto;
    padding: var(--app-gutter) var(--app-gutter) 4rem;
}

/* The toggle shows the theme it switches to; CSS picks the icon, so it survives every re-render. */
.theme-toggle {
    --bs-btn-border-color: var(--bs-border-color);
    line-height: 1;
}

.theme-toggle .theme-toggle-dark,
[data-bs-theme="dark"] .theme-toggle .theme-toggle-light {
    display: none;
}

[data-bs-theme="dark"] .theme-toggle .theme-toggle-dark {
    display: inline;
}

/* Bootstrap keeps outline-secondary the same grey in both themes; on the dark surface it reads as disabled. */
[data-bs-theme="dark"] .btn-outline-secondary {
    --bs-btn-color: var(--bs-body-color);
    --bs-btn-border-color: var(--bs-border-color);
    --bs-btn-hover-color: var(--bs-emphasis-color);
    --bs-btn-hover-bg: var(--bs-secondary-bg);
    --bs-btn-hover-border-color: var(--bs-secondary-color);
}

/* Page furniture ---------------------------------------------------------- */

.page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    margin-bottom: 1.25rem;
}

.page-header h1 {
    letter-spacing: -.015em;
}

/* FocusOnNavigate moves focus to the h1 on every navigation; the ring would read as an error. */
h1:focus {
    outline: none;
}

.section-title {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-bottom: .5rem;
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
}

.section-note {
    font-weight: 400;
    letter-spacing: normal;
    text-transform: none;
}

.section-title::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: var(--bs-border-color);
}

.card {
    border-radius: var(--app-radius);
}

.card > .card-header {
    background: var(--bs-tertiary-bg);
    border-bottom-color: var(--bs-border-color);
    font-weight: 600;
}

.meta-chip {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .15rem .6rem;
    border-radius: 2rem;
    background: var(--bs-secondary-bg);
    font-size: .8125rem;
    color: var(--bs-secondary-color);
}

/* A value and the button that copies it, as one control rather than two things near each other: the button is
   flush against the code it copies, sharing its border, so which value it takes is never a guess. It was
   detached and half-transparent before, and lit up on any hover of the row — a promise of interactivity made
   by the wrong element. */
.copy-pair {
    display: inline-flex;
    align-items: stretch;
    max-width: 100%;
}

.copy-pair > code {
    padding: .1rem .4rem;
    border: 1px solid var(--bs-border-color);
    border-right: 0;
    border-radius: .25rem 0 0 .25rem;
    /* A NodeId is long and a card is not: it scrolls inside its own box rather than widening the page. */
    overflow-x: auto;
    white-space: nowrap;
}

/* The same fill as the code beside it, so the two read as one box and not as a button parked next to a value. */
.copy-pair > .copy-button {
    --bs-btn-bg: var(--bs-secondary-bg);
    border-radius: 0 .25rem .25rem 0;
}

.copy-button {
    --bs-btn-padding-y: .05rem;
    --bs-btn-padding-x: .4rem;
    --bs-btn-font-size: .75rem;
    --bs-btn-border-color: var(--bs-border-color);
    display: inline-flex;
    align-items: center;
    line-height: 1;
}

.copy-button.is-copied {
    --bs-btn-color: var(--bs-success);
    --bs-btn-hover-color: var(--bs-success);
}

.copy-button.is-failed {
    --bs-btn-color: var(--bs-danger);
    --bs-btn-hover-color: var(--bs-danger);
}

/* One icon at a time, chosen by the class the script sets — no second render to keep in step. */
.copy-button .copy-done,
.copy-button .copy-failed,
.copy-button.is-copied .copy-idle,
.copy-button.is-failed .copy-idle {
    display: none;
}

.copy-button.is-copied .copy-done,
.copy-button.is-failed .copy-failed {
    display: inline;
}

.empty-state {
    padding: 2.5rem 1rem;
    text-align: center;
    color: var(--bs-secondary-color);
}

.empty-state > i {
    display: block;
    margin-bottom: .75rem;
    font-size: 1.75rem;
    opacity: .45;
}

code {
    padding: .1rem .3rem;
    border-radius: .25rem;
    background: var(--bs-secondary-bg);
}

/* Tables ------------------------------------------------------------------ */

/* Quieter heads: the labels are structure, the rows are the content. */
.table > thead th {
    border-bottom-color: var(--bs-border-color);
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--bs-secondary-color);
}

/* A table that fills a card takes the card's own gutter, not the table's flush edge. */
.card > .table {
    margin-bottom: 0;
}

.card > .table > thead th {
    background: transparent;
}

.card > .table > :not(caption) > * > *:first-child {
    padding-left: 1rem;
}

.card > .table > :not(caption) > * > *:last-child {
    padding-right: 1rem;
}

/* A right-aligned number needs air before whatever column follows it. */
.table .value-gap {
    padding-right: 1.5rem;
}

/* Rows that select something say so on hover. */
.table-selectable > tbody > tr {
    cursor: pointer;
}

/* Status ------------------------------------------------------------------ */

.status-badge {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    font-weight: 500;
}

.status-badge::before {
    content: "";
    width: .45rem;
    height: .45rem;
    border-radius: 50%;
    background: currentColor;
}

.status-badge.is-live::before {
    animation: status-pulse 2s ease-in-out infinite;
}

@keyframes status-pulse {
    50% {
        opacity: .25;
    }
}

@media (prefers-reduced-motion: reduce) {
    .status-badge.is-live::before {
        animation: none;
    }
}

.endpoint {
    display: flex;
    align-items: center;
    gap: .4rem;
    font-size: .8125rem;
}

.endpoint-protocol {
    font-size: .65rem;
    letter-spacing: .06em;
}

/* Signals ----------------------------------------------------------------- */

.sparkline {
    width: 120px;
    height: 20px;
    color: var(--bs-primary);
}

.signal-value {
    font-family: var(--bs-font-monospace);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

/* The one number the signal page exists to show. */
.signal-reading {
    font-family: var(--bs-font-monospace);
    font-variant-numeric: tabular-nums;
    font-size: 2.25rem;
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -.02em;
    color: var(--bs-emphasis-color);
}

.blazor-error-boundary {
    background: #b32121;
    padding: 1rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/* The preview chart and the state band under it. Colours come from Bootstrap's palette so the band reads in
   both themes without a second stylesheet. */
.curve-chart {
    display: flex;
    align-items: stretch;
    gap: .5rem;
}

.curve-chart svg {
    flex: 1 1 auto;
    height: 180px;
    border: 1px solid var(--bs-border-color);
    border-radius: .25rem;
    background: var(--bs-tertiary-bg);
}

/* The same chart, given the room the signal page has for it. */
.history-chart svg {
    height: 340px;
}

.curve-axis {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    width: 4rem;
}

/* The curve sits in a flex row beside the axis column, so it is narrower than the row by that column plus the
   gap. The band and the time labels share the curve's time axis, so they have to share its width too: left at
   100% they run 72 px past it, and a counter's step is drawn a long way from the band edge that produced it. */
.state-band,
.curve-footer {
    width: calc(100% - 4.5rem);
}

.state-band {
    height: 14px;
    margin-top: .25rem;
    border-radius: .2rem;
    overflow: hidden;
}

.state-band .band-0 { fill: var(--bs-primary); }
.state-band .band-1 { fill: var(--bs-success); }
.state-band .band-2 { fill: var(--bs-warning); }
.state-band .band-3 { fill: var(--bs-info); }
.state-band .band-4 { fill: var(--bs-secondary); }
.state-band .band-5 { fill: var(--bs-danger); }

/* JSON editor ------------------------------------------------------------- */

.json-editor {
    width: 100%;
    height: 520px;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--app-radius);
}

/* The vendored Ace theme is light only. Its dark palette is restated here rather than vendoring a second
   theme file, which would also need its own module URL and its own link in App.razor. */
[data-bs-theme="dark"] .ace-tm {
    background-color: #1b1f24;
    color: #d7dce3;
}

[data-bs-theme="dark"] .ace-tm .ace_gutter {
    background: #16191d;
    color: #7d868f;
}

[data-bs-theme="dark"] .ace-tm .ace_cursor { color: #d7dce3; }
[data-bs-theme="dark"] .ace-tm .ace_string { color: #9ece6a; }
[data-bs-theme="dark"] .ace-tm .ace_variable { color: #7dcfff; }
[data-bs-theme="dark"] .ace-tm .ace_constant.ace_numeric { color: #7aa2f7; }
[data-bs-theme="dark"] .ace-tm .ace_constant.ace_language,
[data-bs-theme="dark"] .ace-tm .ace_keyword { color: #bb9af7; }
[data-bs-theme="dark"] .ace-tm .ace_comment { color: #5c6773; }

[data-bs-theme="dark"] .ace-tm .ace_invalid {
    background-color: rgba(255, 0, 0, .15);
    color: #ff7a7a;
}

[data-bs-theme="dark"] .ace-tm .ace_marker-layer .ace_selection { background: #2b3a55; }
[data-bs-theme="dark"] .ace-tm .ace_marker-layer .ace_active-line { background: rgba(255, 255, 255, .05); }
[data-bs-theme="dark"] .ace-tm .ace_gutter-active-line { background-color: #22262c; }
[data-bs-theme="dark"] .ace-tm .ace_print-margin { background: #2a2f36; }

/* The vendored theme points these at PNGs that were never vendored, so they 404 in both themes. */
.ace-tm .ace_indent-guide,
.ace-tm .ace_indent-guide-active {
    background: none;
}
