/*
  Beacon web client — design tokens and shell.

  Loaded after MudBlazor's stylesheet so these declarations win: MudBlazor is used for its overlay
  services (snackbars, dialogs, popovers) rather than for its look, and its typography and background
  would otherwise override the approved design.

  No webfont is fetched. A CDN is not an option here, and self-hosting the two families would add
  ~200 KB to a payload that already ships a runtime, so the stack degrades to the platform UI font.
*/

:root {
    color-scheme: light;

    --font-ui: "Schibsted Grotesk", "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system,
        "Helvetica Neue", Arial, sans-serif;
    --font-mono: "JetBrains Mono", "Cascadia Mono", ui-monospace, "SF Mono", "Consolas", monospace;

    --canvas: #e8eaed;
    --shell: #f6f7f8;
    --chrome: #fbfbfa;
    --card: #ffffff;
    --line: #e6e8eb;
    --line-strong: #d7dade;

    --ink: #1c1e21;
    --ink-2: #3a3f45;
    --ink-3: #5b6067;
    --muted: #8a9099;

    --primary: oklch(0.52 0.11 230);
    --primary-hover: oklch(0.46 0.11 230);
    --accent: oklch(0.62 0.11 230);
    --tint: oklch(0.94 0.02 230);
    --tint-strong: oklch(0.9 0.03 230);

    --online: oklch(0.6 0.15 150);
    --online-tint: oklch(0.95 0.04 150);
    --live: oklch(0.55 0.19 27);
    --live-tint: oklch(0.95 0.05 27);
    --warn: oklch(0.68 0.14 75);
    --warn-tint: oklch(0.96 0.05 75);

    --video: #14161a;
    --video-offline: #1b1e23;

    --radius-sm: 8px;
    --radius: 10px;
    --radius-lg: 14px;

    --shadow-sm: 0 1px 2px rgb(20 22 26 / 6%), 0 1px 1px rgb(20 22 26 / 4%);
    --shadow: 0 1px 2px rgb(20 22 26 / 5%), 0 6px 16px -8px rgb(20 22 26 / 14%);
    --shadow-lg: 0 2px 4px rgb(20 22 26 / 6%), 0 18px 36px -14px rgb(20 22 26 / 22%);

    --sidebar-width: 244px;

    /*
      The logo's own three colours. Separate tokens rather than reuses of --primary: the mark is drawn on its own
      dark container, so it needs a contrast pairing that holds whatever the surface behind it does — including in
      dark mode, where the container is remapped and these are not.
    */
    --mark-bg: #0F2C3C;
    --mark-arc: #2A92BB;
    --mark-dot: #ffffff;
}

*,
*::before,
*::after {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
    padding: 0;
    height: 100%;
}

body {
    background: var(--canvas);
    color: var(--ink);
    font-family: var(--font-ui);
    font-size: 14px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
h4 {
    margin: 0;
    font-weight: 650;
    letter-spacing: -0.012em;
    color: var(--ink);
}

h1 {
    font-size: 21px;
}

h2 {
    font-size: 16px;
}

h3 {
    font-size: 14px;
}

p {
    margin: 0 0 10px;
    color: var(--ink-2);
}

a {
    color: var(--primary);
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

h1:focus,
h2:focus {
    outline: none;
}

code,
.mono {
    font-family: var(--font-mono);
    font-size: 12.5px;
    font-variant-ligatures: none;
    color: var(--ink-2);
    word-break: break-all;
}

/* Layout shell ------------------------------------------------------------------------------- */

.shell {
    display: flex;
    min-height: 100vh;
    background: var(--shell);
}

.shell__sidebar {
    position: sticky;
    top: 0;
    display: flex;
    flex-direction: column;
    flex: 0 0 var(--sidebar-width);
    width: var(--sidebar-width);
    height: 100vh;
    background: var(--chrome);
    border-right: 1px solid var(--line);
}

.shell__main {
    flex: 1 1 auto;
    min-width: 0;
    background: var(--canvas);
}

.shell__topbar {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: 12px;
    height: 56px;
    padding: 0 22px;
    background: color-mix(in srgb, var(--chrome) 88%, transparent);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--line);
}

.shell__content {
    padding: 22px;
    max-width: 1560px;
}

.shell__scrim {
    display: none;
}

.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 18px 18px 14px;
}

.brand__mark {
    display: flex;
    box-shadow: var(--shadow-sm);
    border-radius: 9px;
}

.mark {
    display: block;
    flex-shrink: 0;
}

.brand__name {
    font-size: 15px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.nav {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 4px 10px 10px;
}

.nav__group {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.nav__link {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    color: var(--ink-2);
    font-size: 13.5px;
    font-weight: 500;
    text-decoration: none;
}

.nav__link:hover {
    background: rgb(28 30 33 / 4%);
    text-decoration: none;
}

.nav__link.active {
    background: var(--tint);
    color: var(--primary);
    font-weight: 600;
}

.nav__link svg {
    flex: 0 0 auto;
    opacity: 0.85;
}

.nav__badge {
    margin-left: auto;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--muted);
}

.nav__footer {
    padding: 10px;
    border-top: 1px solid var(--line);
}

.user {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.user:hover {
    background: rgb(28 30 33 / 4%);
}

.user__avatar {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--tint-strong);
    color: var(--primary);
    font-size: 12px;
    font-weight: 700;
}

.user__lines {
    min-width: 0;
}

.user__name {
    font-size: 13px;
    font-weight: 600;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.user__sub {
    font-size: 11.5px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Type helpers ------------------------------------------------------------------------------- */

.label {
    font-size: 12px;
    font-weight: 700;
    line-height: 1.4;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted);
}

.dim {
    color: var(--ink-3);
}

.muted {
    color: var(--muted);
}

.small {
    font-size: 12.5px;
}

.tabular {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

.stack {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.row--wrap {
    flex-wrap: wrap;
}

.spacer {
    flex: 1 1 auto;
}

/* Page header -------------------------------------------------------------------------------- */

.page-head {
    display: flex;
    align-items: flex-end;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 18px;
}

.page-head__text {
    min-width: 0;
}

.page-head__sub {
    margin: 3px 0 0;
    font-size: 13px;
    color: var(--ink-3);
}

.page-head__actions {
    display: flex;
    gap: 8px;
    margin-left: auto;
}

/* Cards -------------------------------------------------------------------------------------- */

.card {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.card--pad {
    padding: 16px;
}

.card__head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 13px 16px;
    border-bottom: 1px solid var(--line);
}

.card__body {
    padding: 16px;
}

.card__body--flush {
    padding: 0;
}

.card__foot {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid var(--line);
    background: var(--shell);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

.grid {
    display: grid;
    gap: 14px;
}

.grid--stats {
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

.grid--cards {
    grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
}

.grid--wide {
    grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
}

.grid--split {
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    align-items: start;
}

@media (max-width: 1080px) {
    .grid--split {
        grid-template-columns: minmax(0, 1fr);
    }
}

.stat {
    padding: 14px 16px;
}

.stat__value {
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin-top: 6px;
    font-size: 26px;
    font-weight: 660;
    letter-spacing: -0.02em;
    line-height: 1.1;
}

.stat__unit {
    font-size: 12.5px;
    font-weight: 500;
    color: var(--muted);
}

/* Badges and dots ---------------------------------------------------------------------------- */

.badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--shell);
    border: 1px solid var(--line);
    font-size: 11.5px;
    font-weight: 600;
    color: var(--ink-3);
    white-space: nowrap;
}

.badge--online {
    background: var(--online-tint);
    border-color: transparent;
    color: color-mix(in oklab, var(--online) 78%, black);
}

.badge--live {
    background: var(--live-tint);
    border-color: transparent;
    color: var(--live);
}

.badge--warn {
    background: var(--warn-tint);
    border-color: transparent;
    color: color-mix(in oklab, var(--warn) 72%, black);
}

.badge--info {
    background: var(--tint);
    border-color: transparent;
    color: var(--primary);
}

.badge--mono {
    font-family: var(--font-mono);
    font-weight: 500;
}

.dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--muted);
    flex: 0 0 auto;
}

.dot--online {
    background: var(--online);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--online) 18%, transparent);
}

.dot--live {
    background: var(--live);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--live) 18%, transparent);
}

.dot--offline {
    background: #c3c8ce;
}

/* Buttons ------------------------------------------------------------------------------------ */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 32px;
    padding: 0 12px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--card);
    color: var(--ink);
    font: inherit;
    font-size: 13px;
    font-weight: 550;
    cursor: pointer;
    white-space: nowrap;
}

.btn:hover:not(:disabled) {
    background: var(--shell);
    text-decoration: none;
}

.btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.btn--primary {
    background: var(--primary);
    border-color: var(--primary);
    color: #fff;
}

.btn--primary:hover:not(:disabled) {
    background: var(--primary-hover);
    border-color: var(--primary-hover);
}

.btn--danger {
    color: var(--live);
    border-color: color-mix(in srgb, var(--live) 30%, var(--line-strong));
}

.btn--danger:hover:not(:disabled) {
    background: var(--live-tint);
}

.btn--live {
    background: var(--live);
    border-color: var(--live);
    color: #fff;
}

.btn--live:hover:not(:disabled) {
    background: color-mix(in oklab, var(--live) 88%, black);
}

.btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--ink-2);
}

.btn--ghost:hover:not(:disabled) {
    background: rgb(28 30 33 / 5%);
}

.btn--sm {
    min-height: 27px;
    padding: 0 9px;
    font-size: 12.5px;
}

.btn--icon {
    width: 32px;
    min-height: 32px;
    padding: 0;
}

.btn--block {
    width: 100%;
}

.btn-group {
    display: inline-flex;
    gap: 6px;
    flex-wrap: wrap;
}

/* Forms -------------------------------------------------------------------------------------- */

.field {
    display: flex;
    flex-direction: column;
    gap: 5px;
    min-width: 0;
}

.field__label {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ink-2);
}

.field__hint {
    font-size: 12px;
    color: var(--muted);
}

.input,
.select,
.textarea {
    width: 100%;
    min-height: 32px;
    padding: 5px 9px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--card);
    color: var(--ink);
    font: inherit;
    font-size: 13px;
}

.textarea {
    min-height: 70px;
    resize: vertical;
}

.input:focus,
.select:focus,
.textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}

.input--mono {
    font-family: var(--font-mono);
    font-size: 12.5px;
}

.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 12px;
}

.check {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--ink-2);
    cursor: pointer;
}

.check input {
    width: 15px;
    height: 15px;
    accent-color: var(--primary);
}

.switch {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    cursor: pointer;
    font-size: 13px;
}

.switch input {
    appearance: none;
    position: relative;
    width: 34px;
    height: 19px;
    margin: 0;
    border-radius: 999px;
    background: #ced3d9;
    cursor: pointer;
    transition: background 120ms ease;
}

.switch input::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: #fff;
    box-shadow: var(--shadow-sm);
    transition: transform 120ms ease;
}

.switch input:checked {
    background: var(--primary);
}

.switch input:checked::after {
    transform: translateX(15px);
}

.switch input:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Lists and tables --------------------------------------------------------------------------- */

.list {
    display: flex;
    flex-direction: column;
}

.list__item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 16px;
    border-bottom: 1px solid var(--line);
}

.list__item:last-child {
    border-bottom: none;
}

.list__item--link {
    color: inherit;
    text-decoration: none;
}

.list__item--link:hover {
    background: var(--shell);
    text-decoration: none;
}

/*
  A row that is a button rather than a link, without looking like one.

  It exists because the alternative was six inline style declarations repeated at the call site to undo what a browser
  does to a <button>. A row that picks a target is genuinely a button — it changes state on this page rather than
  navigating — and it should still read as a row.
*/
.list__item--button {
    width: 100%;
    background: none;
    border: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

/*
    A relayed camera picture. 16:9 is fixed rather than driven by the incoming frame, so a list of cameras does
    not reflow as each one connects and reports its own aspect.
*/
.relay {
    position: relative;
    aspect-ratio: 16 / 9;
    background: var(--video);
    display: grid;
    place-items: center;
    overflow: hidden;
}

.relay--compact {
    border-radius: 10px 10px 0 0;
}

/*
    Taken out of the flow, so the picture never competes with the panel in front of it.

    As a flex item this element claimed the full width and pushed the idle column — button included — off to one
    side, where it was clipped by the edge of the card. Hiding it did not help either: `.relay video` outranks
    `.is-hidden` on specificity, so `display: none` never applied and the element occupied the row whether there
    was a picture in it or not. Positioning it absolutely settles both, and matches what the camera tile has always
    done to its own video.
*/
.relay video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    background: var(--video);
}

/*
    The idle panel: what the tile says before a picture arrives.

    A centred column with its own measure, above the video — which is absolutely positioned and opaque inside a
    camera tile, and a positioned element paints over a static one regardless of source order. The tile's own
    placeholder has always carried that z-index; this one was written without it and produced a black rectangle
    with an unclickable button underneath.
*/
.relay__idle {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    max-width: min(34ch, 88%);
    padding: 18px;
    text-align: center;
    color: #cfd4da;
}

.relay__idle p {
    margin: 0;
    font-size: 12.5px;
    line-height: 1.45;
    color: #9aa2ab;
}

.relay__idle .label {
    color: #e6e9ec;
}

/*
    Choosing which television relays. A quiet row under the button rather than a form control competing with it:
    it only appears when a camera is attached to more than one TV, and the default is nearly always right.
*/
.relay__through {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-top: 4px;
    border-top: 1px solid rgb(255 255 255 / 10%);
    width: 100%;
    justify-content: center;
}

.relay__through .select {
    min-width: 150px;
    height: 30px;
    padding: 0 26px 0 10px;
    font-size: 12.5px;
    color: #e6e9ec;
    background-color: rgb(255 255 255 / 8%);
    border-color: rgb(255 255 255 / 16%);
}

.relay__through .select option {
    color: var(--ink);
    background: var(--card);
}

/*
    Fills the tile stage rather than sizing to its own content. The stage centres its children, so a relay that
    shrank to fit a button left the picture in a small box in the middle of a large black one.
*/
.tile__stage .relay {
    position: absolute;
    inset: 0;
    aspect-ratio: auto;
    background: transparent;
}

.relay__chip {
    position: absolute;
    bottom: 8px;
    left: 8px;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 8px;
    border-radius: 999px;
    font-size: 11px;
    color: #fff;
    background: rgb(0 0 0 / 55%);
}

.relay__stop {
    position: absolute;
    bottom: 8px;
    right: 8px;
    z-index: 2;
}

.relay__sound {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 2;
}

/*
    The row a library page handed you over to. Without it, landing on a device page with forty media items and
    being told "the one you picked is in there somewhere" is worse than not carrying the selection at all.
*/
.list__item--picked {
    background: color-mix(in srgb, var(--accent) 9%, transparent);
    box-shadow: inset 2px 0 0 var(--accent);
}

.list__title {
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ink);
}

.list__sub {
    font-size: 12px;
    color: var(--muted);
}

.table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.table th {
    padding: 9px 16px;
    text-align: left;
    font-size: 11.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}

.table td {
    padding: 10px 16px;
    border-bottom: 1px solid var(--line);
    color: var(--ink-2);
    vertical-align: middle;
}

.table tr:last-child td {
    border-bottom: none;
}

.table__scroll {
    overflow-x: auto;
}

/* Video and camera tiles --------------------------------------------------------------------- */

.tile {
    position: relative;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-radius: var(--radius-lg);
    border: 1px solid var(--line);
    background: var(--card);
    box-shadow: var(--shadow-sm);
}

.tile__stage {
    position: relative;
    aspect-ratio: 16 / 9;
    background: var(--video);
    color: #cfd4da;
    display: grid;
    place-items: center;
    text-align: center;
    padding: 18px;
}

.tile__stage--offline {
    background: var(--video-offline);
}

.tile__stage video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    background: var(--video);
}

.tile__placeholder {
    /* Sits above the absolutely positioned <video>, so a preview that is off still shows its explanation. */
    position: relative;
    z-index: 1;
    max-width: 42ch;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
}

.tile__placeholder p {
    margin: 0;
    font-size: 12px;
    line-height: 1.5;
    color: #9aa1a9;
}

.tile__placeholder .label {
    color: #7d848c;
}

.tile__overlay {
    position: absolute;
    top: 10px;
    left: 10px;
    display: flex;
    gap: 6px;
    z-index: 2;
}

.tile__chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border-radius: 999px;
    background: rgb(0 0 0 / 55%);
    color: #eef1f4;
    font-size: 11px;
    font-weight: 600;
    backdrop-filter: blur(4px);
}

.tile__bar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-top: 1px solid var(--line);
}

.tile__name {
    font-size: 13.5px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tile__meta {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--muted);
}

.tile__rail {
    border-top: 1px solid var(--line);
    background: var(--shell);
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/* PTZ ---------------------------------------------------------------------------------------- */

.ptz {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
}

.dpad {
    display: grid;
    grid-template-columns: repeat(3, 28px);
    grid-template-rows: repeat(3, 28px);
    gap: 3px;
}

.dpad button {
    display: grid;
    place-items: center;
    border: 1px solid var(--line-strong);
    border-radius: 6px;
    background: var(--card);
    color: var(--ink-2);
    cursor: pointer;
    padding: 0;
}

.dpad button:hover:not(:disabled) {
    background: var(--tint);
    color: var(--primary);
}

.dpad button:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.dpad__center {
    display: grid;
    place-items: center;
    font-size: 10px;
    font-weight: 700;
    color: var(--muted);
}

.zoom {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.presets {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    align-items: center;
}

.preset {
    padding: 2px 8px;
    border: 1px solid var(--line-strong);
    border-radius: 999px;
    background: var(--card);
    font-size: 11.5px;
    font-weight: 600;
    color: var(--ink-2);
    cursor: pointer;
}

.preset:hover {
    background: var(--tint);
    color: var(--primary);
}

/* Meters and progress ------------------------------------------------------------------------ */

.meter {
    position: relative;
    height: 6px;
    border-radius: 999px;
    background: var(--line);
    overflow: hidden;
}

.meter__fill {
    position: absolute;
    inset: 0 auto 0 0;
    background: var(--primary);
    border-radius: 999px;
    transition: width 90ms linear;
}

.meter__fill--level {
    background: linear-gradient(90deg, var(--online), var(--warn) 78%, var(--live));
}

.level-bars {
    display: flex;
    align-items: flex-end;
    gap: 3px;
    height: 34px;
}

.level-bars span {
    flex: 1 1 0;
    border-radius: 2px;
    background: var(--line);
    min-height: 3px;
}

.level-bars span.on {
    background: var(--online);
}

.level-bars span.hot {
    background: var(--live);
}

/* States ------------------------------------------------------------------------------------- */

.empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 42px 20px;
    text-align: center;
    color: var(--ink-3);
}

.empty__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--ink);
}

.empty__body {
    max-width: 46ch;
    font-size: 13px;
    color: var(--ink-3);
}

.skeleton {
    border-radius: var(--radius);
    background: linear-gradient(90deg, var(--line) 25%, #eff1f3 37%, var(--line) 63%);
    background-size: 400% 100%;
    animation: shimmer 1.3s ease-in-out infinite;
    height: 14px;
}

@keyframes shimmer {
    0% {
        background-position: 100% 0;
    }

    100% {
        background-position: 0 0;
    }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton {
        animation: none;
    }
}

.notice {
    display: flex;
    gap: 10px;
    padding: 11px 13px;
    border-radius: var(--radius);
    border: 1px solid var(--line);
    background: var(--shell);
    font-size: 12.5px;
    color: var(--ink-2);
}

.notice--warn {
    background: var(--warn-tint);
    border-color: color-mix(in srgb, var(--warn) 32%, transparent);
}

.notice--error {
    background: var(--live-tint);
    border-color: color-mix(in srgb, var(--live) 28%, transparent);
    color: color-mix(in oklab, var(--live) 82%, black);
}

.notice--info {
    background: var(--tint);
    border-color: color-mix(in srgb, var(--primary) 20%, transparent);
    color: color-mix(in oklab, var(--primary) 86%, black);
}

.notice__title {
    font-weight: 700;
}

/* Modal -------------------------------------------------------------------------------------- */

.modal {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: grid;
    place-items: center;
    padding: 20px;
    background: rgb(20 22 26 / 42%);
}

.modal__panel {
    width: 100%;
    max-width: 520px;
    max-height: 86vh;
    overflow-y: auto;
    background: var(--card);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
}

.modal__panel--wide {
    max-width: 760px;
}

/* Authentication screen ----------------------------------------------------------------------- */

.auth {
    display: grid;
    place-items: center;
    min-height: 100vh;
    padding: 24px;
    background: var(--shell);
}

.auth__panel {
    width: 100%;
    max-width: 400px;
    box-shadow: var(--shadow);
}

.auth__panel h2 {
    margin-bottom: 6px;
}

/* Loading splash (index.html) ---------------------------------------------------------------- */

.boot {
    position: fixed;
    inset: 0;
    display: grid;
    place-items: center;
    background: var(--shell);
}

.boot__inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    color: var(--muted);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.boot__track {
    width: 168px;
    height: 4px;
    border-radius: 999px;
    background: var(--line);
    overflow: hidden;
}

.boot__track::after {
    content: "";
    display: block;
    height: 100%;
    width: var(--blazor-load-percentage, 15%);
    background: var(--primary);
    border-radius: 999px;
    transition: width 120ms linear;
}

#blazor-error-ui {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 2000;
    display: none;
    padding: 12px 18px;
    background: #1c1e21;
    color: #fff;
    font-size: 13px;
    box-shadow: 0 -2px 14px rgb(0 0 0 / 22%);
}

#blazor-error-ui .dismiss {
    float: right;
    cursor: pointer;
    padding-left: 14px;
}

#blazor-error-ui a {
    color: #9ecbff;
}

.blazor-error-boundary {
    padding: 14px 16px;
    border-radius: var(--radius);
    background: var(--live-tint);
    color: color-mix(in oklab, var(--live) 82%, black);
    font-size: 13px;
}

.blazor-error-boundary::after {
    content: "Something in this section failed to render. Reload the page.";
}

/* Now playing --------------------------------------------------------------------------------- */

/*
  The one surface that answers "what is this television doing".

  Two displays used to compete here — Beacon's own playback in a status card, another app's in a card of its own
  further down — so this deliberately has room for both at once: the screen owner as the headline, and whatever else
  is making sound underneath it. The art panel on the left is dark on purpose; it stands in for a screen.
*/
.stage {
    display: grid;
    grid-template-columns: 340px 1fr;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}

.stage--live {
    border-color: var(--live);
    box-shadow: 0 0 0 1px var(--live), var(--shadow-sm);
}

.stage__art {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 216px;
    padding: 24px;
    background: var(--video);
    color: #cfd4da;
}

.stage--offline .stage__art {
    background: var(--video-offline);
}

.stage__badge {
    position: absolute;
    top: 12px;
    left: 12px;
    padding: 3px 9px;
    border-radius: var(--radius-sm);
    background: rgb(20 22 26 / 66%);
    border: 1px solid rgb(255 255 255 / 10%);
    font-size: 11px;
    font-weight: 600;
    color: #cfd4da;
}

.stage__glyph {
    display: grid;
    place-items: center;
    width: 54px;
    height: 54px;
    border-radius: var(--radius-lg);
    background: #fff;
    color: var(--video);
}

.stage--live .stage__glyph {
    background: var(--live);
    color: #fff;
}

.stage__caption {
    font-family: var(--font-mono);
    font-size: 11px;
    color: #7d858f;
    text-align: center;
}

/* The picture fills the panel it replaces; the glyph state keeps the same box so nothing shifts when one arrives. */
.stage__shot {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.stage__caption--over {
    position: absolute;
    bottom: 10px;
    left: 12px;
    right: 12px;
    padding: 3px 8px;
    border-radius: var(--radius-sm);
    background: rgb(20 22 26 / 66%);
    text-align: left;
}

.stage__body {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 20px 22px;
    min-width: 0;
}

.stage__title h2 {
    font-size: 21px;
    letter-spacing: -0.014em;
}

.stage__sub {
    margin: 3px 0 0;
    font-size: 13.5px;
    color: var(--ink-3);
}

.stage__note {
    margin: 0;
    font-size: 12px;
    line-height: 1.5;
    color: var(--muted);
}

.stage__actions {
    gap: 8px;
}

/* Another app's sound behind Beacon's picture. Divided, not boxed: it is part of the same answer. */
.stage__also {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    padding-top: 12px;
    border-top: 1px solid var(--line);
}

.stage__also-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
    flex: 1 1 200px;
}

/* Transport ----------------------------------------------------------------------------------- */

/*
  Buttons for a session Beacon does not own. Round, so they do not read as Beacon's own actions, and drawn only for
  the transport the session actually advertises.
*/
.transport {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.transport__btn {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 999px;
    border: 1px solid var(--line-strong);
    background: var(--card);
    color: var(--ink-2);
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}

.transport__btn:hover {
    border-color: var(--accent);
    background: var(--tint);
}

.transport__btn--main {
    width: 46px;
    height: 46px;
    border-color: var(--ink);
    background: var(--ink);
    color: #fff;
}

.transport__btn--main:hover {
    border-color: var(--ink);
    background: var(--ink-2);
}

.transport--compact .transport__btn {
    width: 30px;
    height: 30px;
    border-radius: var(--radius-sm);
}

.transport--compact .transport__btn--main {
    width: 30px;
    height: 30px;
}

.transport__rule {
    width: 1px;
    height: 22px;
    background: var(--line);
    margin: 0 2px;
}

/* Control-centre tabs -------------------------------------------------------------------------- */

.stage-tabs {
    display: flex;
    gap: 2px;
    overflow-x: auto;
    scrollbar-width: none;
    border-bottom: 1px solid var(--line);
}

.stage-tabs::-webkit-scrollbar {
    display: none;
}

.stage-tab {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    flex-shrink: 0;
    padding: 9px 14px;
    border: 0;
    border-bottom: 2px solid transparent;
    background: none;
    font: inherit;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ink-3);
    cursor: pointer;
    white-space: nowrap;
}

.stage-tab:hover {
    color: var(--ink);
}

.stage-tab--on {
    border-bottom-color: var(--primary);
    color: var(--primary);
}

/* Device rows --------------------------------------------------------------------------------- */

/*
  One row per television, because the list is read down a column: name, what it is doing, what to press. Cards made
  that scan impossible and left no room for the line that matters most — what another app is playing.
*/
.device-row {
    display: grid;
    grid-template-columns: 220px minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 18px;
    padding: 14px 18px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 12px;
    box-shadow: var(--shadow-sm);
    transition: border-color 120ms ease;
}

.device-row:hover {
    border-color: var(--accent);
}

.device-row--live,
.device-row--live:hover {
    border-color: var(--live);
}

.device-row--offline {
    background: var(--chrome);
}

.device-row__who,
.device-row__doing {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.device-row__who:hover,
.device-row__doing:hover {
    text-decoration: none;
}

.device-row__names,
.device-row__lines {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.device-row__name {
    font-size: 14.5px;
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.device-row__model,
.device-row__sub {
    font-size: 12px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.device-row__headline {
    font-size: 13.5px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.device-row--offline .device-row__headline {
    color: var(--ink-3);
}

.device-row__glyph {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--radius-sm);
    background: var(--shell);
    border: 1px solid var(--line);
    color: var(--ink-2);
}

.device-row__glyph--live {
    background: var(--live);
    border-color: var(--live);
    color: #fff;
}

.device-row__transport,
.device-row__act {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Call stage ----------------------------------------------------------------------------------- */

/*
  A video call, laid out the way video calls have been laid out since the idea existed: the person you are talking to
  fills the frame and you sit in the corner.

  Here "the person" is a room — a television has no camera, so the large picture is a camera on its network relayed by
  the TV. Either view can be promoted, and that promotion is a class change and nothing else: both video elements keep
  their position in the markup, because moving a video element in the DOM detaches the stream attached to it.
*/
.callstage {
    position: relative;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: var(--radius-lg);
    border: 1px solid var(--line);
    background: var(--video);
    box-shadow: var(--shadow);
}

.callstage--live {
    border-color: var(--live);
    box-shadow: 0 0 0 1px var(--live), var(--shadow);
}

.callstage__view {
    position: absolute;
    overflow: hidden;
    background: var(--video);
    transition: inset 180ms ease, width 180ms ease;
}

.callstage__view--main {
    inset: 0;
    z-index: 1;
}

/*
  Bottom-left, clear of the control bar. Left rather than right because the bar's right end holds the button that
  ends the call, and a picture you can tap to enlarge should not share an edge with that.
*/
.callstage__view--pip {
    z-index: 3;
    left: 14px;
    bottom: 76px;
    width: 25%;
    min-width: 132px;
    max-width: 260px;
    aspect-ratio: 16 / 9;
    inset-block-start: auto;
    border-radius: var(--radius);
    border: 1px solid rgb(255 255 255 / 20%);
    box-shadow: var(--shadow-lg);
    background: var(--video-offline);
    cursor: pointer;
}

.callstage__view--pip:hover {
    border-color: rgb(255 255 255 / 42%);
}

.callstage__view video,
.callstage__view .relay {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

.callstage__view video {
    object-fit: cover;
    background: var(--video);
}

/* The relay component brings its own aspect ratio and surface; inside here the slot owns both. */
.callstage__view .relay {
    aspect-ratio: auto;
    background: transparent;
    border-radius: 0;
}

.callstage__tag {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 2;
    max-width: calc(100% - 16px);
    padding: 3px 8px;
    border-radius: var(--radius-sm);
    background: rgb(20 22 26 / 66%);
    font-size: 11px;
    font-weight: 600;
    color: #e6e9ec;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The main view's own tag would sit under the status chips, so it moves out of their way. */
.callstage__view--main .callstage__tag {
    top: auto;
    bottom: 76px;
}

.callstage__empty {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 18px;
    text-align: center;
    color: #9aa1a9;
}

.callstage__empty strong {
    font-size: 14px;
    color: #e6e9ec;
}

.callstage__empty p {
    margin: 0;
    font-size: 12px;
    line-height: 1.5;
    max-width: 340px;
}

.callstage__view--pip .callstage__empty p {
    display: none;
}

.callstage__chips {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 4;
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

/* Over the picture rather than beside it: on a call, the picture is the page. */
.callstage__bar {
    position: absolute;
    inset: auto 0 0 0;
    z-index: 4;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 14px;
    background: linear-gradient(to top, rgb(14 16 19 / 82%), rgb(14 16 19 / 0%));
}

.callstage__gap {
    flex: 1 1 auto;
}

.callstage__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    height: 40px;
    min-width: 40px;
    padding: 0 12px;
    border-radius: 999px;
    border: 1px solid rgb(255 255 255 / 20%);
    background: rgb(38 42 48 / 88%);
    color: #f2f4f6;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease;
}

.callstage__btn:hover:not(:disabled) {
    background: rgb(58 63 70 / 92%);
    border-color: rgb(255 255 255 / 34%);
}

.callstage__btn:disabled {
    opacity: 0.5;
    cursor: default;
}

/* Muted or hidden reads as inverted, the way it does on a phone: the control is on, the thing it governs is off. */
.callstage__btn--off {
    background: #f2f4f6;
    border-color: #f2f4f6;
    color: var(--video);
}

.callstage__btn--call {
    background: var(--live);
    border-color: var(--live);
}

.callstage__btn--call:hover:not(:disabled) {
    background: oklch(0.5 0.19 27);
    border-color: oklch(0.5 0.19 27);
}

.callstage__btn--end {
    background: var(--live);
    border-color: var(--live);
}

.callstage__btn--wide {
    padding: 0 16px;
}

/* Folded sections ----------------------------------------------------------------------------- */

/*
  What you read once, out of the way of what you act on. A real <details>, so it survives a re-render — a
  component-held flag would slam shut every time the hub pushes a state change.
*/
.fold {
    background: var(--chrome);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.fold__head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 13px 16px;
    cursor: pointer;
    list-style: none;
    user-select: none;
}

.fold__head::-webkit-details-marker {
    display: none;
}

.fold__title {
    font-size: 14px;
    font-weight: 700;
    color: var(--ink-2);
}

.fold__note {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--muted);
}

.fold__chevron {
    display: grid;
    place-items: center;
    color: var(--muted);
    transition: transform 140ms ease;
}

.fold[open] .fold__chevron {
    transform: rotate(180deg);
}

.fold__body {
    padding: 14px 16px 16px;
    border-top: 1px solid var(--line);
}

/* Narrow screens ----------------------------------------------------------------------------- */

@media (max-width: 860px) {
    .shell__sidebar {
        position: fixed;
        z-index: 60;
        transform: translateX(-100%);
        transition: transform 160ms ease;
        box-shadow: var(--shadow-lg);
    }

    .shell[data-nav="open"] .shell__sidebar {
        transform: translateX(0);
    }

    .shell[data-nav="open"] .shell__scrim {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 50;
        background: rgb(20 22 26 / 34%);
    }

    .shell__content {
        padding: 16px;
    }

    /*
      One-handed. This is read on a phone at the front door as often as on a desktop, so the things worth doing in a
      hurry become a row of full-height targets rather than a wrapped line of small buttons.
    */
    .page-head__actions {
        display: grid;
        grid-auto-flow: column;
        grid-auto-columns: 1fr;
        gap: 8px;
        width: 100%;
        margin-left: 0;
    }

    .page-head__actions > .btn {
        justify-content: center;
        height: 44px;
    }

    /* The shell header already carries a way back, so a "All devices" link does not earn a third of this row. */
    .page-head__actions > .btn--ghost:first-child {
        display: none;
    }

    .stage {
        grid-template-columns: 1fr;
    }

    /* A strip rather than a square: vertical space on a phone belongs to the words, not to a placeholder. */
    .stage__art {
        flex-direction: row;
        justify-content: flex-start;
        gap: 14px;
        min-height: 0;
        padding: 14px 16px;
    }

    .stage__badge {
        position: static;
        margin-left: auto;
    }

    .stage__glyph {
        width: 42px;
        height: 42px;
        border-radius: var(--radius);
    }

    .stage__body {
        padding: 16px;
    }

    .stage__title h2 {
        font-size: 18px;
    }

    /*
      Two rows: who and what on top, controls beneath. The transport keeps its own line because a play button
      squeezed beside a title is a play button you press by accident.
    */
    .device-row {
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 10px 12px;
        padding: 12px 14px;
    }

    .device-row__doing {
        grid-column: 1 / -1;
    }

    .device-row__transport {
        grid-column: 1;
    }

    .device-row__transport:empty {
        display: none;
    }

    .device-row__act {
        grid-column: 2;
        justify-content: flex-end;
    }

    .device-row__act .btn {
        height: 38px;
    }

    /*
      A phone in portrait has no room for a 16:9 picture with a second picture inside it, so the corner view shrinks
      and the control bar is allowed to wrap rather than pushing its buttons off the edge.
    */
    .callstage__view--pip {
        left: 10px;
        bottom: 68px;
        width: 34%;
        min-width: 104px;
    }

    .callstage__bar {
        flex-wrap: wrap;
        padding: 10px;
    }

    .callstage__gap {
        flex-basis: 0;
    }

    .callstage__view--main .callstage__tag,
    .callstage__empty p {
        display: none;
    }

    /*
      The tab strip follows you down the card instead of scrolling away, which is the mobile answer to reaching
      Cameras from the bottom of a long list. A bar pinned to the viewport was the other option and was rejected: it
      would still be sitting there while you read the settings it does not control.
    */
    .stage-tabs {
        position: sticky;
        top: 0;
        z-index: 2;
        margin: 0 -16px;
        padding: 8px 16px;
        background: var(--card);
        border-bottom: 1px solid var(--line);
    }
}

@media (min-width: 861px) {
    .nav-toggle {
        display: none;
    }
}

/*
  Dark mode is honoured by remapping the tokens rather than restyling components: the video and tile
  surfaces are already dark by design, so only the light surfaces need new values.
*/
@media (prefers-color-scheme: dark) {
    :root {
        color-scheme: dark;

        --canvas: #16181b;
        --shell: #1b1e21;
        --chrome: #1e2124;
        --card: #22262a;
        --line: #2e3338;
        --line-strong: #3a4046;

        --ink: #eceef0;
        --ink-2: #c3c8ce;
        --ink-3: #a1a8b0;
        --muted: #7d848c;

        --primary: oklch(0.68 0.11 230);
        --primary-hover: oklch(0.74 0.11 230);
        --accent: oklch(0.72 0.1 230);
        --tint: oklch(0.32 0.05 230);
        --tint-strong: oklch(0.38 0.06 230);

        --online-tint: oklch(0.32 0.06 150);
        --live-tint: oklch(0.32 0.08 27);
        --warn-tint: oklch(0.33 0.07 75);

        --shadow-sm: 0 1px 2px rgb(0 0 0 / 34%);
        --shadow: 0 2px 10px rgb(0 0 0 / 40%);
        --shadow-lg: 0 18px 40px -12px rgb(0 0 0 / 62%);
    }

    .nav__link:hover,
    .user:hover,
    .btn--ghost:hover:not(:disabled) {
        background: rgb(255 255 255 / 6%);
    }

    .badge--online {
        color: oklch(0.82 0.14 150);
    }

    .badge--live {
        color: oklch(0.75 0.16 27);
    }

    .badge--warn {
        color: oklch(0.84 0.12 75);
    }

    .notice--error {
        color: oklch(0.82 0.13 27);
    }

    .notice--info {
        color: oklch(0.85 0.07 230);
    }

    .switch input {
        background: #4a5157;
    }

    .skeleton {
        background: linear-gradient(90deg, #262b30 25%, #2f353b 37%, #262b30 63%);
        background-size: 400% 100%;
    }
}

/* Picture-in-picture for the TV's return video during a call. */
.tile__pip {
    position: absolute;
    right: 12px;
    bottom: 12px;
    width: 180px;
    max-width: 40%;
    aspect-ratio: 16 / 9;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.16);
    background: #1b1e23;
    object-fit: cover;
}

.is-hidden { display: none; }
