/* IPTV Auth admin interface.
   One stylesheet, no framework, no external fonts: this UI has to work on an
   isolated network and while the thing it manages is broken. */

/* Alteox brand palette, from the press kit. Kept as its own layer so the
   semantic variables below can be read against it, and so nothing has to guess
   at a hex code twice. */
:root {
  --brand-light-blue: #92DAEB;
  --brand-medium-blue: #6ABDD8;
  --brand-blue: #3880BB;
  --brand-bright-blue: #107CB5;
  --brand-purple: #293C9F;
  --brand-pink-purple: #8D63EE;
  --brand-dark-blue: #062535;
  --brand-dark: #060D11;
}

/* Semantic layer, matching alteox.com: white ground, near-black body text,
   brand blue for headings and links, and the dark navy reserved for the one
   primary action on a page -- the same role "Contact Us" plays on the site. */
:root {
  --bg: #ffffff;
  --panel: #ffffff;
  --panel-2: #f2f6f9;
  --border: #dfe7ed;
  --text: #171717;
  --muted: #5c6f7c;

  /* Links, headings and the active navigation item. */
  --accent: var(--brand-blue);
  /* The primary button. */
  --cta: var(--brand-dark-blue);
  --cta-text: #ffffff;
  --accent-text: #ffffff;

  --ok: #1a7f4b;
  --ok-bg: #e6f5ec;
  --warn: #8a5a00;
  --warn-bg: #fdf3e0;
  --err: #b5262f;
  --err-bg: #fdeaec;

  --radius: 12px;
  /* Buttons and chips are pills on the site, so they are pills here. */
  --radius-pill: 999px;
  --shadow: 0 1px 2px rgba(6, 37, 53, .04), 0 8px 24px rgba(6, 37, 53, .05);

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  /* The site sets "creato"; Creato Display is the licensed family name. Neither
     is bundled -- this UI loads no external fonts, because it has to work on an
     isolated network -- so both are named ahead of the system stack. */
  --sans: "Creato Display", creato, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Built up from the brand darks rather than a neutral grey, so the dark
       theme reads as the same family as the light one. */
    --bg: var(--brand-dark);
    --panel: #0c1a23;
    --panel-2: #12252f;
    --border: #1d3a49;
    --text: #e6eef2;
    --muted: #8fa5b1;
    --accent: var(--brand-medium-blue);
    --accent-text: var(--brand-dark);
    /* On a dark ground the navy CTA would disappear, so the light blue carries
       it instead and the text goes dark. */
    --cta: var(--brand-medium-blue);
    --cta-text: var(--brand-dark);
    --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
    --ok: #56d68f;
    --ok-bg: #14301f;
    --warn: #e5b567;
    --warn-bg: #33280f;
    --err: #ef8a92;
    --err-bg: #38191c;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  background-image: linear-gradient(180deg, #f3f8fb 0%, var(--bg) 420px);
  background-repeat: no-repeat;
  -webkit-text-size-adjust: 100%;
}

@media (prefers-color-scheme: dark) {
  body { background-image: linear-gradient(180deg, #0b1c26 0%, var(--bg) 420px); }
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ------------------------------------------------------------------ layout */

.shell { display: flex; min-height: 100vh; }

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

.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 18px 16px 14px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}
.brand:hover { text-decoration: none; }

/* The logo is used as supplied and never recoloured or rearranged, per the brand
   guidelines, so it is sized by height alone and left to keep its own ratio. */
.brand-logo {
  height: 26px;
  width: auto;
  display: block;
}

/* "Auth" names this tool next to the company mark, rather than being set into
   the logo itself. */
.brand-sub {
  font-weight: 600;
  font-size: 13px;
  letter-spacing: .02em;
  color: var(--muted);
}

/* The supplied light variant is greyscale, so on a dark background the full
   colour mark is kept and only lifted slightly off the panel. */
@media (prefers-color-scheme: dark) {
  .brand-logo { filter: saturate(1.1) brightness(1.12); }
}

.nav { list-style: none; margin: 0; padding: 10px 8px; flex: 1; overflow-y: auto; }
.nav li { margin: 1px 0; }
.nav a {
  display: block;
  padding: 7px 12px;
  border-radius: var(--radius-pill);
  color: var(--text);
  font-weight: 500;
}
/* Not --panel-2: that is the sidebar's own colour, so the hover was invisible. */
.nav a:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); text-decoration: none; }
.nav a.active:hover { background: var(--accent); }
.nav a.active { background: var(--accent); color: var(--accent-text); }
.nav-sep { height: 1px; background: var(--border); margin: 10px 6px; }

.sidebar-foot { padding: 12px; border-top: 1px solid var(--border); }
.who { display: block; margin-bottom: 8px; color: var(--text); }
.who:hover { text-decoration: none; }
.who-name {
  display: block;
  font-size: 12px;
  color: var(--muted);
  overflow-wrap: anywhere;
}
/* Which reseller this login works inside. Reads as a label rather than as part of the address,
   because two tabs signed in as different accounts otherwise look the same. */
.who-sub {
  display: block;
  margin-top: 3px;
  font-size: 11px;
  font-weight: 600;
  color: var(--accent);
  overflow-wrap: anywhere;
}

.content { flex: 1; padding: 24px 28px 60px; min-width: 0; max-width: 1400px; }

.centered {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

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

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
h1 {
  font-size: 24px;
  margin: 0 0 4px;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -.01em;
  color: var(--accent);
}
h2 { font-size: 15px; margin: 0 0 10px; font-weight: 700; }
h3 { font-weight: 700; }
.subtitle { color: var(--muted); margin: 0; font-size: 13px; }
.actions { display: flex; gap: 8px; flex-wrap: wrap; }

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

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px;
  margin-bottom: 16px;
}
.panel-tight { padding: 0; overflow: hidden; }
.panel h2 { margin-top: 0; }
.panel-note { color: var(--muted); font-size: 13px; margin: 0 0 12px; }

.grid { display: grid; gap: 16px; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.grid-stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }

.stat {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px 18px;
}
.stat-value { color: var(--accent); }
.stat-value { font-size: 24px; font-weight: 650; font-variant-numeric: tabular-nums; }
.stat-label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
/* A word rather than a number -- "per session", "1m" -- at a size that stays inside the tile,
   with the line height made up so the label below sits level with its neighbours'. */
.stat-value.stat-text { font-size: 18px; line-height: 2; overflow-wrap: anywhere; }

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

.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
th { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); font-weight: 600; white-space: nowrap; }
tbody tr:hover { background: var(--panel-2); }
tbody tr:has(> td.empty):hover, tbody tr.rowform:hover { background: transparent; }
tbody tr:last-child td { border-bottom: none; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.nowrap, th.nowrap { white-space: nowrap; }
.mono { font-family: var(--mono); font-size: 12px; }
.empty { padding: 28px 16px; text-align: center; color: var(--muted); }

/* A quieter aside inside a heading or a label -- the word that says what the
   value beside it is, rather than being the value. */
.meta { color: var(--muted); font-weight: 400; }

/* --------------------------------------------------------------- team rows */

/* One collapsed row per team member. <details> is used rather than a scripted
   accordion because the pages carry no inline script under their CSP, and this
   needs none: the browser does the toggling. */
.member {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  margin-bottom: 8px;
}
.member[open] { border-color: var(--accent); }

.member > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 14px;
  cursor: pointer;
  list-style: none;          /* Firefox */
}
.member > summary::-webkit-details-marker { display: none; }
.member > summary:hover { background: var(--panel-2); border-radius: var(--radius); }

.member-who { display: flex; align-items: center; gap: 8px; min-width: 0; }
.member-who strong { overflow-wrap: anywhere; }
.member-badges { display: flex; gap: 6px; flex-wrap: wrap; }

/* Pushed to the right on a wide screen, and simply wraps on a narrow one. */
.member-meta { margin-left: auto; color: var(--muted); font-size: 12px; white-space: nowrap; }

.member-toggle {
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  padding: 2px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
}
.member[open] .member-toggle { color: var(--muted); }
.member[open] .member-toggle::after { content: " \2013 close"; }

.member-body { padding: 4px 14px 14px; border-top: 1px solid var(--border); margin-top: 2px; }

.member-danger {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--border);
}
.member-danger .hint { margin: 0; }

/* The role key above the list: two columns, badge then meaning. Full width, because the table
   otherwise shrink-wraps to its widest nowrap cell and the prose row is left in a narrow column. */
.roles-key { width: 100%; }
.roles-key td { border: 0; padding: 3px 10px 3px 0; vertical-align: top; font-size: 13px; }
.roles-key td:first-child { width: 116px; white-space: nowrap; }
/* A row spanning both columns is prose, so it wraps. Without this it inherits both the nowrap
   and the fixed width the badge column needs, and runs off the panel mid-sentence. */
.roles-key td[colspan] { white-space: normal; width: auto; padding-top: 8px; }
.roles-key td:last-child { color: var(--muted); }
.roles-key td:last-child strong { color: var(--text); }

/* A heading that divides a page into sections, below the h1 but above a panel. */
.section-head { font-size: 15px; margin: 22px 0 4px; }
.section-note { margin: 0 0 8px; }

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

.badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 600;
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--border);
  white-space: nowrap;
}
.badge.ok { background: var(--ok-bg); color: var(--ok); border-color: transparent; }
.badge.warn { background: var(--warn-bg); color: var(--warn); border-color: transparent; }
.badge.err { background: var(--err-bg); color: var(--err); border-color: transparent; }
.badge.role { text-transform: capitalize; }
/* Server kind next to a mapping, so "which of these is MediaMTX" is readable
   without knowing the naming convention someone used for their servers.
   Composes with .ok/.warn/.err, which set only colour -- so a row of server
   labels keeps one size and one typeface and varies only in state. */
.badge.kind { font-family: var(--mono); font-weight: 500; font-size: 10px; }
/* Several of these sit in one cell, so they need to breathe without the template
   inserting whitespace that a mono font would render as a wide gap. */
.badge.kind + .badge.kind { margin-left: 3px; }

/* ------------------------------------------------------------------- forms */

label { display: block; font-weight: 550; margin-bottom: 4px; font-size: 13px; }
.hint { color: var(--muted); font-weight: 400; font-size: 12px; margin-top: 3px; }

/* Styled by exclusion rather than by listing the types we know about.
   
   The list was the bug, twice. An attribute selector matches the attribute and not the
   property, so <input name=x> matched nothing and rendered as a white box in a dark
   interface; that was fixed by adding :not([type]). Then a type="url" field did the same
   thing, because url was never in the list either -- and the next one to be added would
   have done it again. Naming what must NOT look like a text field is a short, closed list;
   naming everything that should is an open one that quietly loses. */
input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=submit]):not([type=button]):not([type=reset]):not([type=range]):not([type=color]):not([type=image]):not([type=hidden]),
select, textarea {
  width: 100%;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  font: inherit;
}
textarea { min-height: 70px; resize: vertical; font-family: var(--mono); font-size: 12px; }
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}

.field { margin-bottom: 14px; }
.field-row { display: flex; gap: 12px; flex-wrap: wrap; }
.field-row > .field { flex: 1 1 200px; }
.field-inline { display: flex; gap: 8px; align-items: center; }
.field-inline input[type=checkbox] { width: auto; margin: 0; }
.field-inline label { margin: 0; font-weight: 500; }

fieldset { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; margin: 0 0 14px; }
legend { padding: 0 6px; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); font-weight: 600; }

/* A checkbox with a word beside it. The disabled state is the part that earns its keep: the
   Active box on the device channel editor is locked until the channel has somewhere to pull
   from, and a checkbox that silently ignores clicks reads as a broken page. */
/* An editable table row: fields side by side, wrapping rather than forcing the table wide. */
.row-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.row-form input[type=text] { width: auto; min-width: 160px; margin: 0; }

/* A text field that suggests as you type.
   A native datalist would do the matching for free, but its popup is drawn by the browser and
   cannot be styled, so on this page it arrived as a piece of operating system in the middle of
   the panel. This is the same behaviour built from the page's own tokens. */
.combo { position: relative; }
.combo-list {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  /* About eight rows, then it scrolls: a popup taller than that covers the fields it is meant
     to be filling in. */
  max-height: 296px;
  overflow-y: auto;
}
.combo-list[hidden] { display: none; }
.combo-option {
  padding: 7px 10px;
  border-radius: 8px;
  font-size: 13px;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  gap: 10px;
  align-items: baseline;
}
.combo-option:hover { background: var(--panel-2); }
/* The keyboard's position, which has to be visible without a pointer anywhere near it. */
.combo-option[aria-selected=true] { background: var(--accent); color: var(--accent-text); }
.combo-option[aria-selected=true] .combo-meta { color: inherit; opacity: .8; }
.combo-meta { color: var(--muted); font-size: 12px; white-space: nowrap; }
.combo-more {
  padding: 6px 10px;
  font-size: 12px;
  color: var(--muted);
  border-top: 1px solid var(--border);
  margin-top: 4px;
}

.check { display: inline-flex; align-items: center; gap: 6px; }
.check input { width: auto; margin: 0; }
.check input:disabled { cursor: not-allowed; opacity: 0.6; }
.check input:disabled ~ .hint { opacity: 0.55; }

/* The Playlists / Channels switch on the subscriber form. Two mutually exclusive views of
   one field, not two fields -- both selections are submitted either way. */
.seg {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  margin-bottom: 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
}
.seg-btn {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  padding: 5px 12px;
  border: 0;
  border-radius: calc(var(--radius) - 2px);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
}
.seg-btn:hover { color: var(--text); }
.seg-btn.is-on {
  background: var(--panel-2);
  color: var(--text);
}
/* Empty when nothing is selected, so a tab reads as a plain label rather than "0". */
.seg-count:empty { display: none; }
.seg-count {
  min-width: 1.4em;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--border);
  color: var(--text);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.seg-btn.is-on .seg-count { background: var(--panel); }
.seg { background: var(--panel); }

.checklist {
  /* Tall enough to choose from without scrolling for a typical line-up. It grows with the
     viewport rather than being a fixed number of pixels, because the useful height is
     "as much of the list as fits" -- 260px showed about eight channels of a hundred, so
     building a playlist meant scrolling a small window. */
  max-height: min(60vh, 620px);
  min-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  background: var(--panel);
}
.checklist label {
  display: flex;
  gap: 8px;
  align-items: baseline;
  font-weight: 450;
  padding: 3px 0;
  margin: 0;
}
.checklist input { width: auto; margin: 0; }
.checklist .meta { color: var(--muted); font-size: 12px; }

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

.btn {
  display: inline-block;
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-weight: 550;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.btn:hover { background: var(--panel-2); text-decoration: none; }
.btn-primary { background: var(--cta); border-color: var(--cta); color: var(--cta-text); }
.btn-primary:hover { filter: brightness(1.12); background: var(--cta); }
.btn-danger { color: var(--err); border-color: var(--err); background: transparent; }
.btn-danger:hover { background: var(--err-bg); }
.btn-quiet { color: var(--muted); font-weight: 500; }
.btn-sm { padding: 3px 9px; font-size: 12px; }
.btn-block { display: block; width: 100%; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
/* Inputs draw their own ring above; these are the other things a keyboard lands on. */
.btn:focus-visible, .nav a:focus-visible, .seg-btn:focus-visible, .alert-close:focus-visible,
.input-clear:focus-visible, .member > summary:focus-visible, .who:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Padded to the same inset as a panel's fields, so the primary button lines up with the
   inputs it applies to instead of with the page edge. */
.btn-row {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 16px;
  padding: 0 18px;
}
.btn-row .spacer { flex: 1; }

/* The save bar of a form taller than the screen. Stuck to the bottom while the form is in
   view, so Save is never two screens below the field that was just changed; it scrolls away
   with the form, so the panels after it are not covered. */
.form-actions {
  position: sticky;
  bottom: 0;
  z-index: 5;
  margin: 16px 0 0;
  padding: 12px 18px;
  background: var(--bg);
  background: color-mix(in srgb, var(--bg) 90%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-top: 1px solid var(--border);
}

/* ------------------------------------------------------------------ alerts */

.alert {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 10px 14px;
  border-radius: var(--radius);
  margin-bottom: 16px;
  font-weight: 500;
}
.alert > span { flex: 1; }
.alert-close {
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-size: 18px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
  opacity: .7;
}
.alert-close:hover { opacity: 1; }
.alert-ok { background: var(--ok-bg); color: var(--ok); }
.alert-err { background: var(--err-bg); color: var(--err); }
.alert-warn { background: var(--warn-bg); color: var(--warn); }

/* ------------------------------------------------------------------ filters */

.filters { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 14px; }
.filters .field { margin: 0; }
.filters input[type=search] { min-width: 220px; }
.filters select { min-width: 130px; }

/* ------------------------------------------------------------- bulk actions */

.bulkbar {
  display: none;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
  margin-bottom: 12px;
}
.bulkbar.on { display: flex; }
.bulkbar .count { font-weight: 600; }
.bulkbar select, .bulkbar input { width: auto; }

/* -------------------------------------------------------------- code blocks */

pre.snippet {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.55;
  margin: 0 0 10px;
  white-space: pre;
}
.secret-once {
  background: var(--warn-bg);
  color: var(--warn);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 14px;
}
.secret-once code {
  display: block;
  font-family: var(--mono);
  font-size: 13px;
  margin-top: 6px;
  overflow-wrap: anywhere;
  user-select: all;
}

/* ------------------------------------------------------------------- login */

.login-card { width: 100%; max-width: 360px; }
/* The sign-in card is the one place with room to give the mark some presence, so
   it is centred and larger than the sidebar copy. */
.login-card .brand {
  border: none;
  padding: 0 0 18px;
  justify-content: center;
}
.login-card .brand-logo { height: 38px; }
/* The word between the password form and the SSO button, ruled on both sides. */
.login-or {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 0;
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .06em;
}
.login-or::before, .login-or::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* -------------------------------------------------------------- pagination */

.pager { display: flex; gap: 8px; align-items: center; justify-content: space-between; margin-top: 14px; }
.pager .info { color: var(--muted); font-size: 12px; }

/* ------------------------------------------------------------------ mobile */

/* The sidebar becomes a header: the mark and the account on one row, the menu as a strip
   that scrolls sideways beneath it. It used to wrap into five rows and a full-width sign-out
   button, which put the first line of every page a whole screen down. */
@media (max-width: 800px) {
  .shell { flex-direction: column; }
  .sidebar {
    width: auto;
    flex: none;
    height: auto;
    position: static;
    border-right: none;
    border-bottom: 1px solid var(--border);
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "brand foot" "nav nav";
    align-items: center;
  }
  .brand { grid-area: brand; border-bottom: none; padding: 10px 14px; }
  .sidebar-foot {
    grid-area: foot;
    border-top: none;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
  }
  .who { margin: 0; min-width: 0; text-align: right; }
  .who-name { max-width: 40vw; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .sidebar-foot .btn-block { display: inline-block; width: auto; padding: 5px 12px; font-size: 12px; }
  .nav {
    grid-area: nav;
    display: flex;
    flex-wrap: nowrap;
    gap: 2px;
    overflow-x: auto;
    padding: 0 8px 8px;
    scrollbar-width: none;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav li { margin: 0; flex: none; }
  .nav a { white-space: nowrap; padding: 6px 12px; }
  .nav-sep { display: none; }
  .content { padding: 16px; }
  .form-actions { padding: 10px 12px; }
}

/* ------------------------------------------------------------- logo picker */

.logo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 12px;
}

.logo-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  background: var(--panel);
}
.logo-card-selected { border-color: var(--accent); }

/* Channel logos are nearly all transparent, and they come in two kinds: white
   lettering meant for a dark background, and dark lettering meant for a light one.
   Either one is invisible against a tile of its own colour, so the preview is a
   chequerboard -- which shows the transparency -- and can be flipped between dark and
   light, because no single choice can display both.

   Dark by default: broadcaster logos are overwhelmingly the white-on-transparent
   kind, and this interface is dark. */
.logo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 84px;
  margin-bottom: 8px;
  border-radius: var(--radius);
  background-color: #1b2b34;
  background-image:
    linear-gradient(45deg, #243b47 25%, transparent 25%, transparent 75%, #243b47 75%),
    linear-gradient(45deg, #243b47 25%, transparent 25%, transparent 75%, #243b47 75%);
  background-size: 16px 16px;
  background-position: 0 0, 8px 8px;
}
.logo-preview img { max-width: 100%; max-height: 76px; object-fit: contain; }

/* Flipped by the button above the grid, for a logo drawn in dark ink. */
body.logos-on-light .logo-preview {
  background-color: #ffffff;
  background-image:
    linear-gradient(45deg, #e2e8ec 25%, transparent 25%, transparent 75%, #e2e8ec 75%),
    linear-gradient(45deg, #e2e8ec 25%, transparent 25%, transparent 75%, #e2e8ec 75%);
}

/* In the light theme the sensible default is the other way round. */
@media (prefers-color-scheme: light) {
  .logo-preview {
    background-color: #ffffff;
    background-image:
      linear-gradient(45deg, #e2e8ec 25%, transparent 25%, transparent 75%, #e2e8ec 75%),
      linear-gradient(45deg, #e2e8ec 25%, transparent 25%, transparent 75%, #e2e8ec 75%);
  }
  body.logos-on-light .logo-preview {
    background-color: #1b2b34;
    background-image:
      linear-gradient(45deg, #243b47 25%, transparent 25%, transparent 75%, #243b47 75%),
      linear-gradient(45deg, #243b47 25%, transparent 25%, transparent 75%, #243b47 75%);
  }
}

.logo-meta { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
.logo-card .hint { margin: 0; }
.logo-card .btn-row { margin-top: 8px; gap: 6px; }

/* A panel for something an operator should look at but that is not an error. The
   guide fed by two sources is the case this exists for: the listings are correct,
   and the configuration is still not what anyone intended. */
.panel-warn {
  border-color: var(--warn);
  background: var(--warn-bg);
}
.panel-warn h2 {
  margin-top: 0;
  color: var(--warn);
  font-size: 1rem;
}

/* A list that reads as prose rather than as a bulleted list, for short enumerations
   inside a panel that is already making a point in sentences. */
ul.plain {
  list-style: none;
  margin: 0.6rem 0;
  padding: 0;
}
ul.plain li {
  padding: 0.15rem 0;
}

/* A row for something deliberately switched off. Dimmed rather than hidden: an
   operator needs to see that they ignored a feed channel, or the decision becomes
   invisible and looks like a channel the feed stopped carrying. */
.row-muted td { opacity: 0.55; }

/* ---------------------------------------------------------------- row forms */

/* An editor that opens as its own full-width row beneath the record it edits.
   The alternative was a form inside a cell, which stretches the row to the height of
   the form and squeezes the fields into one column's width -- so the fields drift away
   from the row they belong to and nothing lines up. */
.rowform > td { padding: 0; background: var(--panel-2); }
.rowform-body {
  padding: 16px 18px;
  border-left: 3px solid var(--accent);
}
.rowform-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-size: 13px;
}
/* The footer keeps the switch that decides whether the device runs next to the button
   that saves it, rather than leaving it among the text fields where it reads as one more
   detail. */
.rowform-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding-top: 6px;
  border-top: 1px solid var(--border);
}
.rowform-actions { display: flex; gap: 8px; }
/* Deleting sits apart from saving, so the two are not adjacent buttons on one line. */
.rowform-danger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--border);
}
.btn.btn-danger { color: var(--err); border-color: var(--err); }
.btn.btn-danger:hover { background: var(--err-bg); }

/* A value meant to be copied whole -- an endpoint, a key -- needs the width to show it.
   At a field's natural width these read as truncated strings nobody trusts. */
.input-wide { width: 100%; }

/* A search box with its own clear button, for a filter that submits to the server: the
   browser's native × on a search input only empties the field, which leaves the page still
   showing the filtered result. This one is a link back to the unfiltered page. */
.input-clearable { position: relative; display: block; }
.input-clearable input { padding-right: 30px; }
.input-clear {
  position: absolute;
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  text-decoration: none;
}
.input-clear:hover { color: var(--text); }
/* WebKit draws its own x in a search field, which sat on top of ours. */
.input-clearable input[type=search]::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

/* One address on Generate link: its name, an optional "i", and Copy. The URL is not
   displayed; it rides in a hidden input for the button and in the row's tooltip. */
.link-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
}
.link-row:last-child { border-bottom: 0; }
.link-name { flex: 1; font-weight: 400; }
.link-row .help { margin-left: 0; }

/* A protocol heading inside a Generate link panel: every SRT resolution under one, then
   HLS, then DASH, in the order the rest of the interface lists them. */
.link-proto {
  margin: 14px 0 4px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--text);
}
.link-proto:first-of-type { margin-top: 0; }

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

/* The explanation behind a small "i", next to the label it is about. A native <details>
   rather than a scripted tooltip: it opens without JavaScript, the keyboard reaches it, and
   the text stays in the page for anyone searching it. The body is a popover so opening one
   does not push the form about. */
.help {
  display: inline-block;
  position: relative;
  vertical-align: middle;
  margin-left: 6px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}
.help > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid var(--muted);
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  font-style: italic;
  font-family: Georgia, "Times New Roman", serif;
  line-height: 1;
  user-select: none;
}
.help > summary::-webkit-details-marker { display: none; }
.help > summary:hover, .help[open] > summary { color: var(--accent); border-color: var(--accent); }
.help-body {
  position: absolute;
  z-index: 40;
  top: calc(100% + 6px);
  left: -8px;
  width: min(440px, calc(100vw - 32px));
  padding: 10px 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  color: var(--text);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.5;
  white-space: normal;
}
/* Flipped by the script when the default position would run off the right edge. */
.help.help-flip .help-body { left: auto; right: -8px; }
.help-body p { margin: 0 0 6px; }
.help-body p:last-child { margin: 0; }
/* A label or heading with an "i" beside it shrinks to its text so the two sit on one line;
   the field below is full width regardless. */
label:has(+ .help), h2:has(+ .help), legend:has(+ .help) { display: inline-block; }
h2:has(+ .help) { margin-right: 0; }
/* Inside a hint paragraph the "i" sits at the end of the visible sentence. */
.hint .help, .panel-note .help { margin-left: 4px; vertical-align: -3px; }

/* Visually hidden, still read by a screen reader. For a column whose header would be
   noise on screen -- the remove button's -- but which is not nameless to somebody
   navigating the table by its headings. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
