/* mcdtv portal. Dark only: this runs on TVs and phones in dark rooms, and a
   light theme nobody asked for is just more surface to keep consistent. */

:root {
  --bg: #0b0e14;
  --bg-2: #11151d;
  --card: #151b25;
  --card-2: #1b2230;
  --line: #262e3d;
  --line-2: #33405a;
  --fg: #e8eef7;
  --muted: #8b97ac;
  --muted-2: #5d6879;
  --accent: #5aa9ff;
  --accent-dim: #1d4a7d;
  --ok: #3fcf8e;
  --warn: #e3b341;
  --bad: #f7757c;
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --plex: #e5a00d;

  --r-sm: 8px;
  --r: 12px;
  --r-lg: 18px;
  --shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  --header-h: 56px;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

/* Touch feel. Android paints a blue flash on every tap by default; a pressed
   control darkens and settles instead, the way a native app's does. A long
   press on a control should not select its label either. */
html { -webkit-tap-highlight-color: transparent; }
button, .btn, a.lab-tile, [role="button"] { -webkit-user-select: none; user-select: none; }
@media (hover: none) {
  button:not(:disabled):active, .btn:active, a.lab-tile:active {
    transform: scale(.97); filter: brightness(1.18);
  }
}
@media (prefers-reduced-motion: reduce) {
  button:not(:disabled):active, .btn:active, a.lab-tile:active { transform: none; }
}
/* Loading placeholders: bars where the rows will be, with a slow sheen. */
.skel { display: flex; flex-direction: column; gap: 10px; padding: 4px 0; }
.skel i {
  display: block; height: 12px; border-radius: 6px;
  background: linear-gradient(90deg, var(--card-2) 0%, var(--line) 50%, var(--card-2) 100%);
  background-size: 200% 100%; animation: skel 1.4s ease-in-out infinite;
}
@keyframes skel { from { background-position: 100% 0; } to { background-position: -100% 0; } }
@media (prefers-reduced-motion: reduce) { .skel i { animation: none; } }

/* Behind an open dialog the page holds still. */
html.modal-open body { overflow: hidden; }

html, body {
  margin: 0; padding: 0; min-height: 100%;
  background: var(--bg); color: var(--fg);
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 15px; line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}
body { overflow-x: hidden; }

a { color: var(--accent); text-decoration: none; }
@media (hover: hover) and (pointer: fine) {
  a:hover { text-decoration: underline; }
}

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

header {
  position: sticky; top: 0; z-index: 40;
  height: var(--header-h);
  display: flex; align-items: center; gap: 20px;
  padding: 0 16px;
  border-bottom: 1px solid var(--line);
  background: rgba(11, 14, 20, 0.86);
  backdrop-filter: saturate(150%) blur(12px);
  -webkit-backdrop-filter: saturate(150%) blur(12px);
}
.brand {
  display: flex; align-items: center; gap: 9px;
  color: var(--fg); font-weight: 650; font-size: 17px; letter-spacing: -0.01em;
}
@media (hover: hover) and (pointer: fine) {
  .brand:hover { text-decoration: none; }
}
.brand img { display: block; }
/* On a 412px phone the wordmark ate ~140px of a header whose only other job is
   the tab row, so the tabs were always clipped. The icon still links home. */
@media (max-width: 700px) {
  .brand span { display: none; }
}

/* The tab row scrolls rather than pushing the header wider than the screen.
   Without min-width:0 this flex child refuses to shrink below its content, so
   the four tabs forced a 466px floor on the whole document: every phone in
   portrait (360-430px) then got shrink-to-fit zoomed by the browser, and the
   Settings tab sat off-screen with no way to reach it. Same idiom as .seg. */
nav#tabs {
  display: flex; gap: 2px; margin-left: auto;
  min-width: 0; overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
nav#tabs::-webkit-scrollbar { display: none; }
nav#tabs button {
  appearance: none; background: transparent; border: 0;
  color: var(--muted); font: inherit; font-weight: 550;
  padding: 8px 16px; border-radius: var(--r-sm); cursor: pointer;
  min-height: 38px; transition: color .12s, background .12s;
  flex: 0 0 auto; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 7px;
}
nav#tabs button .icon { display: grid; opacity: .85; }
nav#tabs button[aria-current="page"] .icon { color: var(--accent); opacity: 1; }
@media (hover: hover) and (pointer: fine) {
  nav#tabs button:hover { color: var(--fg); background: var(--card); }
}
nav#tabs button[aria-current="page"] { color: var(--fg); background: var(--card-2); }
/* Ten labeled tabs need about 1050px laid out in a row. Between there and
   the tablet layout the row tightens and the wordmark gives way to the icon. */
@media (max-width: 1365px) {
  header { gap: 12px; }
  .brand span { display: none; }
  nav#tabs button { padding: 8px 10px; gap: 6px; }
}
/* Up to 1100px (the Fold unfolded is about 770 to 880) the tabs are an
   app's tab bar: the icon over a small label. Where they all fit they share
   the width, centered; on a phone the row scrolls and the tab showing sits
   in the middle (main.js), so the tabs a swipe reaches are always in view.
   Labels stay at every width: icons alone left ten tabs to guesswork. */
@media (max-width: 1100px) {
  header { padding: 0; gap: 0; }
  header .brand { display: none; }
  nav#tabs {
    margin-left: 0; flex: 1 1 auto; gap: 0; height: 100%;
    scroll-snap-type: x proximity; overscroll-behavior-x: contain;
  }
  nav#tabs button {
    position: relative; flex: 1 0 auto; min-width: 60px; max-width: 112px;
    flex-direction: column; justify-content: center; gap: 3px;
    padding: 6px 6px 7px; border-radius: 0; scroll-snap-align: center;
    font-size: 11px; font-weight: 600; letter-spacing: 0;
  }
  nav#tabs button:first-child { margin-left: auto; }
  nav#tabs button:last-child { margin-right: auto; }
  nav#tabs button .icon svg { width: 22px; height: 22px; }
  nav#tabs button[aria-current="page"] { background: transparent; }
  nav#tabs button[aria-current="page"] .tab-label { color: var(--fg); }
  /* The bar under the tab showing, like an app's tab indicator. */
  nav#tabs button::after {
    content: ""; position: absolute; left: 50%; bottom: 0;
    width: 28px; height: 3px; border-radius: 3px 3px 0 0;
    background: var(--accent); transform: translateX(-50%) scaleX(0);
    transition: transform .2s var(--ease-out);
  }
  nav#tabs button[aria-current="page"]::after { transform: translateX(-50%) scaleX(1); }
  /* Where more tabs wait past an edge, that edge fades out. */
  nav#tabs.more-right { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); }
  nav#tabs.more-left { -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent); }
  nav#tabs.more-left.more-right { -webkit-mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent); }
}
@media (max-width: 1100px) and (hover: hover) and (pointer: fine) {
  nav#tabs button:hover { background: var(--card); }
}
@media (max-width: 1100px) and (hover: none) {
  nav#tabs button:hover { background: transparent; color: var(--muted); }
  nav#tabs button[aria-current="page"]:hover { color: var(--fg); }
}
@media (prefers-reduced-motion: reduce) {
  nav#tabs button::after { transition: none; }
}

/* Swiping between tabs (swipe.js). The browser keeps vertical scrolling and
   pinch zoom; the sideways part of a swipe is the page's. Chrome's own
   sideways history swipe would otherwise fight it. */
@media (hover: none) and (pointer: coarse) {
  html.swipe-on main { touch-action: pan-y pinch-zoom; }
  html.swipe-on, html.swipe-on body { overscroll-behavior-x: none; }
}
/* The chip at the screen edge naming the tab a swipe is heading for. */
.swipe-peek {
  position: fixed; z-index: 45; top: 50%; left: 10px;
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px 10px 12px; border-radius: 999px;
  background: var(--card-2); border: 1px solid var(--line-2); color: var(--muted);
  box-shadow: var(--shadow); font-weight: 600; font-size: 14px;
  pointer-events: none; transform: translate(-110%, -50%); opacity: 0;
  transition: background .15s, color .15s, border-color .15s;
}
.swipe-peek.from-right { left: auto; right: 10px; transform: translate(110%, -50%); }
.swipe-peek .swipe-peek-icon { display: grid; }
.swipe-peek.armed { background: var(--accent-dim); border-color: var(--accent); color: var(--fg); }
.swipe-peek.armed .swipe-peek-icon { color: var(--accent); }

/* main fills the screen under the header even when a tab is short, so a
   swipe anywhere on the page reaches swipe.js. */
main {
  padding: 20px 16px 96px;
  max-width: 1500px; margin: 0 auto;
  min-height: calc(100dvh - var(--header-h));
}
/* The 96px at the foot keeps the last row clear of the floating players. A
   phone has no now-playing button, so it keeps the room only while the
   music dock or the TV mini player shows. */
@media (max-width: 700px) {
  main { padding-bottom: calc(28px + env(safe-area-inset-bottom, 0px)); }
  body:has(#dock:not([hidden])) main, body:has(#tv-mini:not([hidden])) main {
    padding-bottom: calc(96px + env(safe-area-inset-bottom, 0px));
  }
}

/* ---------- primitives ---------- */

.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r); padding: 16px;
}
.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.spacer { flex: 1 1 auto; }
.muted { color: var(--muted); }
.dim { color: var(--muted-2); }
.small { font-size: 13px; }
.tiny { font-size: 12px; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Two lines then ellipsis. Better than truncate for titles, where the episode
   name carries the useful half: "Blaze and the Monster Machines - The Ice
   Treasure" cut to one line loses exactly the part you wanted. */
.clamp-2 {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
}

h2 { margin: 0 0 4px; font-size: 19px; font-weight: 640; letter-spacing: -0.01em; }
h3 { margin: 0 0 10px; font-size: 15px; font-weight: 620; }
.section { margin-bottom: 26px; }
.section-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; }
.section-head h2 { margin: 0; }

button, .btn {
  appearance: none; font: inherit; cursor: pointer;
  background: var(--card-2); color: var(--fg);
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 8px 14px; min-height: 38px;
  transition: background .12s, border-color .12s, transform .1s ease, filter .1s ease;
}
@media (hover: hover) and (pointer: fine) {
  button:hover, .btn:hover { background: var(--line); text-decoration: none; }
}
button:disabled { opacity: .45; cursor: not-allowed; }
button.primary { background: var(--accent-dim); border-color: var(--accent); color: #eaf4ff; }
@media (hover: hover) and (pointer: fine) {
  button.primary:hover { background: #245b98; }
}
button.ghost { background: transparent; }
button.danger { border-color: #6b2b30; color: var(--bad); }
@media (hover: hover) and (pointer: fine) {
  button.danger:hover { background: #2a1418; }
}
button.sm { padding: 4px 10px; min-height: 30px; font-size: 13px; }

input, select, textarea {
  font: inherit; color: var(--fg);
  background: var(--bg-2); border: 1px solid var(--line-2);
  border-radius: var(--r-sm); padding: 8px 11px; min-height: 38px;
  width: 100%;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent);
}
label.field { display: block; margin-bottom: 12px; }
label.field > span { display: block; font-size: 13px; color: var(--muted); margin-bottom: 5px; }
.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0 14px; }

.pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; border-radius: 999px;
  font-size: 12px; font-weight: 560; line-height: 1.7;
  background: var(--card-2); color: var(--muted);
  border: 1px solid var(--line);
  white-space: nowrap;
}
.pill.ok { color: var(--ok); border-color: #1d5c41; background: #10241c; }
.pill.warn { color: var(--warn); border-color: #5c4a1d; background: #241f10; }
.pill.bad { color: var(--bad); border-color: #6b2b30; background: #241316; }
.pill.accent { color: var(--accent); border-color: var(--accent-dim); background: #101f30; }
.pill.plex { color: var(--plex); border-color: #5c4508; background: #241d08; }

.empty {
  padding: 34px 20px; text-align: center; color: var(--muted);
  border: 1px dashed var(--line-2); border-radius: var(--r);
}
/* A phone has no room to spare on a box that says "nothing here". */
@media (max-width: 559px) { .empty { padding: 18px 14px; } }

/* segmented sub-navigation */
.seg {
  display: flex; gap: 2px; padding: 3px;
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); overflow-x: auto; scrollbar-width: none;
}
.seg::-webkit-scrollbar { display: none; }
.seg button {
  background: transparent; border: 0; min-height: 32px;
  padding: 5px 13px; font-size: 13.5px; color: var(--muted);
  border-radius: 6px; white-space: nowrap;
}
.seg button[aria-current="true"] { background: var(--card-2); color: var(--fg); }
/* Activity has six sub-views. On a phone they do not fit one line, and a
   horizontal scroll hides the tail: "Logs" sat off the right edge with nothing
   indicating it existed. Wrapping shows all of them at the cost of one extra
   row, which is the better trade on the screen where they are hardest to find. */
@media (max-width: 700px) {
  .seg { flex-wrap: wrap; }
  /* Activity's six fill two even rows of three rather than a ragged wrap. */
  .seg.act-nav { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .seg.act-nav button { padding: 5px 4px; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
}
.plex-filters > .pill { margin-left: auto; }
/* On a phone the pill wraps under the filters; it starts the line there. */
@media (max-width: 559px) { .plex-filters > .pill { margin-left: 0; } }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
  text-align: left; font-weight: 560; font-size: 12px;
  text-transform: uppercase; letter-spacing: .04em;
  /* --muted-2 measured 3.06:1 against the card, under the 4.5 floor. */
  color: var(--muted); padding: 0 10px 8px; border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
td { padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
@media (hover: hover) and (pointer: fine) {
  tbody tr:hover { background: var(--card); }
}
.table-wrap { overflow-x: auto; }
/* A phone gets the source, its channel count and its state; the kind and
   the last fetch time are a desk-sized detail. */
@media (max-width: 560px) {
  .sources-table th:nth-child(2), .sources-table td:nth-child(2),
  .sources-table th:nth-child(4), .sources-table td:nth-child(4) { display: none; }
}

/* ---------- toast + modal ---------- */

#toast {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 90; max-width: 92vw;
  background: var(--card-2); color: var(--fg);
  border: 1px solid var(--line-2); border-radius: var(--r);
  padding: 11px 18px; box-shadow: var(--shadow);
}
#toast.bad { border-color: #6b2b30; }

#modal {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(0, 0, 0, .66);
  display: flex; align-items: center; justify-content: center; padding: 18px;
  opacity: 0; transition: opacity .22s ease;
}
#modal.open { opacity: 1; }
.modal-card {
  position: relative;
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); box-shadow: var(--shadow);
  padding: 20px; width: min(760px, 100%);
  max-height: 88vh; overflow: auto; overscroll-behavior: contain;
  transform: translateY(10px) scale(.98);
  transition: transform .24s cubic-bezier(.2, .8, .2, 1);
}
#modal.open .modal-card { transform: none; }
/* On a phone a dialog is a bottom sheet: full width, anchored to the bottom
   edge within thumb reach, sliding up, with a grab handle to swipe it away. */
@media (max-width: 600px) {
  #modal { align-items: flex-end; padding: 0; }
  .modal-card, .modal-card:has(.cover-modal), .modal-card:has(.speaker-modal) {
    width: 100%; max-height: 92dvh;
    border-radius: 20px 20px 0 0; border-bottom: 0;
    padding: 24px 14px calc(16px + env(safe-area-inset-bottom));
    transform: translateY(100%);
  }
  .modal-card::before {
    content: ""; position: absolute; top: 9px; left: 50%;
    width: 38px; height: 4px; margin-left: -19px;
    border-radius: 2px; background: var(--line-2);
  }
}
@media (prefers-reduced-motion: reduce) {
  #modal, .modal-card { transition: none; }
}

/* ---------- tiles and progress (the Desk's TV favorites, Activity) ---------- */

.progress {
  height: 3px; border-radius: 2px; background: var(--line);
  overflow: hidden; margin-top: 7px;
}
.progress > i { display: block; height: 100%; background: var(--accent); }

.tile {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r); padding: 10px; cursor: pointer;
  display: flex; flex-direction: column; gap: 7px;
  text-align: left; min-height: 0; min-width: 0;
  transition: border-color .12s, background .12s;
}
.tile > * { min-width: 0; }
@media (hover: hover) and (pointer: fine) {
  .tile:hover { border-color: var(--line-2); background: var(--card-2); }
}
.tile[aria-current="true"] { border-color: var(--accent); background: #101c2b; }
.tile-logo {
  height: 44px; display: flex; align-items: center; justify-content: center;
}
.tile-logo img { max-height: 44px; max-width: 100%; object-fit: contain; }
.tile-logo .fallback {
  /* --muted, not --muted-2: the dimmer grey measured under 4.5:1 on a tile. */
  font-size: 12px; color: var(--muted); font-weight: 600;
  text-align: center; line-height: 1.2;
}
.tile-name { font-size: 13px; font-weight: 560; line-height: 1.25; }
.tile-now { font-size: 11.5px; color: var(--muted); line-height: 1.3; }

/* ---------- activity ---------- */

.stat-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.stat { background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: 14px; }
.stat .v { font-size: 26px; font-weight: 660; letter-spacing: -0.02em; line-height: 1.15; }
.stat .v small { font-size: .55em; font-weight: 600; color: var(--muted); letter-spacing: 0; margin-left: 3px; }
.stat .k { font-size: 12px; color: var(--muted); margin-top: 2px; }
/* Nothing happening reads as quiet, not as a headline. */
.stat.zero .v { color: var(--muted); }
/* A phone shows the four as one row: as two rows of big tiles they took
   most of the first screen to say "0". */
@media (max-width: 559px) {
  .stat-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }
  .stat { padding: 9px 6px; text-align: center; }
  .stat .v { font-size: 20px; }
  .stat .v small { display: block; margin: 0; font-size: 11px; }
  .stat .k { font-size: 11px; line-height: 1.25; }
}

/* min(330px, 100%): on the Fold's 344px cover screen the column is 312px,
   and a 330px floor pushed every card 18px past the screen's edge. */
.live-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr));
}
.live-card {
  background: var(--card); border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--r); padding: 13px 15px;
  display: grid; gap: 9px;
  /* A grid track will not shrink below its item's min-content width, and a
     nowrap title has a min-content width of the whole string. Without this the
     card grows to fit "Blaze and the Monster Machines - The Ice Treasure" and
     spills past its own border instead of the text truncating. */
  min-width: 0;
}
.live-card > * { min-width: 0; }
.live-card.plex { border-left-color: var(--plex); }
.live-head { display: flex; align-items: center; gap: 10px; }
.live-who { font-weight: 620; }
.live-body { display: flex; gap: 11px; align-items: flex-start; }
.live-art {
  width: 46px; min-width: 46px; height: 46px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-2); border-radius: var(--r-sm); overflow: hidden;
}
.live-art img { max-width: 100%; max-height: 100%; object-fit: contain; }
.live-art.poster { width: 42px; min-width: 42px; height: 63px; }
.live-art.poster img { width: 100%; height: 100%; object-fit: cover; }
.live-meta { min-width: 0; flex: 1; }
/* "Release line" is a finger target on any narrow screen, not only on a
   coarse pointer: a desk browser squeezed to phone width taps with a mouse
   but still gets the phone layout. */
@media (max-width: 700px) {
  .live-card button { min-height: 44px; }
}

/* The y labels sit in a gutter beside the drawing (see concurrencyChart). */
.chart-box { position: relative; padding-left: 22px; }
.chart-y {
  position: absolute; left: 0; width: 18px; text-align: right;
  font-size: 11px; line-height: 12px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.chart { width: 100%; height: 170px; display: block; }
.chart .axis { stroke: var(--line); stroke-width: 1; }
.chart .cap { stroke: var(--bad); stroke-width: 1; stroke-dasharray: 4 4; opacity: .7; }
.chart-key { display: inline-flex; align-items: center; gap: 6px; }
.chart-key-cap { width: 16px; height: 0; border-top: 2px dashed var(--bad); opacity: .8; }
.chart .area { fill: var(--accent); opacity: .18; }
.chart .line { fill: none; stroke: var(--accent); stroke-width: 1.6; }
.chart-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--muted); }

.timeline { display: grid; gap: 6px; }
.tl-row { display: grid; grid-template-columns: 108px minmax(0, 1fr); gap: 10px; align-items: center; }
.tl-name { font-size: 13px; color: var(--muted); }
.tl-track {
  position: relative; height: 24px;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: 5px;
  overflow: hidden;
}
.tl-block {
  position: absolute; top: 0; bottom: 0;
  background: var(--accent-dim); border-right: 1px solid var(--bg);
  min-width: 2px; cursor: default;
}
@media (hover: hover) and (pointer: fine) {
  .tl-block:hover { background: var(--accent); }
}
.tl-axis {
  display: grid; grid-template-columns: 108px minmax(0, 1fr); gap: 10px;
  font-size: 11px; color: var(--muted-2);
}
.tl-ticks { display: flex; justify-content: space-between; }
.tl-ticks span { white-space: nowrap; }
/* A phone keeps the start, the middle and the end. Five times did not fit
   the track, and each one wrapped onto a second line. */
@media (max-width: 480px) {
  .tl-row, .tl-axis { grid-template-columns: 84px minmax(0, 1fr); }
  .tl-ticks span:nth-child(2), .tl-ticks span:nth-child(4) { display: none; }
}

/* ---------- activity lists: a table on a desk, a card per row on a phone ---------- */

/* See rowTable in activity.js. A cell's r-* class is its place on the card;
   the desk table ignores it. Icons and units ("275 req") exist for the card,
   where no column header explains a pill. */
.rt .rt-unit, .rt td > .icon { display: none; }
.rt td.tone { color: var(--tone); }
.rt .rt-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Truncate long names inside the cell. max-width on a table cell itself is
   ignored by the table's column sizing, so it goes on the text instead. */
.rt div.rt-t, .rt td.solo > .rt-t { display: block; max-width: 340px; }
.rt td.r-meta.solo > .rt-t { max-width: 220px; }
.rt .rt-now { margin-left: 6px; font-size: 12px; font-weight: 600; }
.rt button { display: inline-flex; align-items: center; gap: 5px; }
.rt .rt-name .rt-t { max-width: 180px; }
.rt td.r-act button + button { margin-left: 4px; }
.rt td.r-art { width: 34px; }
.rt-poster img { display: block; width: 30px; height: 45px; object-fit: cover; border-radius: 3px; }
.rt-foot { margin-top: 10px; }
.rt-foot .rt-more { min-height: 44px; }

@media (max-width: 700px) {
  .rt, .rt tbody { display: block; }
  .rt thead { display: none; }
  .rt tr {
    position: relative; display: flex; flex-wrap: wrap; align-items: center;
    gap: 6px; padding: 12px 0; border-bottom: 1px solid var(--line);
  }
  .rt tr:last-child { border-bottom: 0; }
  .rt td { display: block; padding: 0; border: 0; min-width: 0; text-align: left; }
  .rt div.rt-t, .rt td > .rt-t, .rt td.r-meta.solo > .rt-t {
    display: block; max-width: none; min-width: 0;
  }
  /* After the rule above and as specific, so a desk-only part of a cell
     (the Plex address beside its town) really leaves the card. */
  .rt .col-only, .rt div.col-only, .rt td.nil { display: none; }
  .rt .rt-unit { display: inline; }
  .rt td > .icon { display: inline-grid; flex: 0 0 auto; }

  /* Line one: what the row is about, then who. */
  .rt td.r-main { order: 1; flex: 1 1 0; font-weight: 600; }
  .rt td.r-who {
    order: 2; flex: 0 1 auto; max-width: 50%;
    display: inline-flex; align-items: center; gap: 4px;
    font-size: 13px; font-weight: 560; color: var(--muted);
  }
  .rt td.r-who.tone { color: var(--tone); }
  /* The line break between the two lines. */
  .rt tr::after { content: ""; order: 3; flex: 0 0 100%; height: 0; }
  /* Line two: small pills, each led by its icon. */
  .rt td.r-meta {
    order: 5; display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
    padding: 1px 9px 1px 7px; border: 1px solid var(--line); border-radius: 999px;
    background: var(--card-2); color: var(--muted);
    font-size: 12px; font-weight: 520; line-height: 1.7; white-space: nowrap;
    font-family: inherit;
  }
  .rt td.r-meta.tone {
    color: var(--tone);
    border-color: color-mix(in srgb, var(--tone) 40%, transparent);
    background: color-mix(in srgb, var(--tone) 12%, transparent);
  }
  .rt td.r-meta.lead { order: 4; }
  .rt .rt-now { margin-left: 2px; }
  .rt td.r-wide { order: 6; flex: 0 0 100%; }
  .rt td.r-wide > .rt-t {
    white-space: normal; overflow-wrap: anywhere;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  }
  /* The row's buttons share the bottom edge at full finger size, whatever
     the pointer: a narrow desk window gets this layout too. */
  .rt td.r-act { order: 7; flex: 0 0 100%; display: flex; gap: 8px; margin-top: 2px; }
  .rt td.r-act button { flex: 1 1 0; justify-content: center; }
  .rt td.r-act button + button { margin-left: 0; }
  .rt button { min-height: 44px; }
  .rt .rt-name { max-width: 100%; }
  /* A poster sits to the left of both lines. */
  .rt.with-art tr { padding-left: 52px; min-height: 84px; }
  .rt td.r-art { position: absolute; left: 0; top: 12px; width: auto; }
  .rt-poster img { width: 40px; height: 60px; }
  .rt-foot .rt-more { flex: 1 1 auto; }
}

/* ---------- the now-playing FAB ---------- */

.fab {
  position: fixed; right: 18px; bottom: 18px; z-index: 70;
  display: flex; align-items: center; gap: 9px;
  padding: 10px 16px; min-height: 46px;
  border-radius: 999px; border: 1px solid var(--line-2);
  background: var(--card-2); box-shadow: var(--shadow);
  font-weight: 560;
}
@media (hover: hover) and (pointer: fine) {
  .fab:hover { background: var(--line); }
}
/* The Activity tab already lists every mcdtv and Plex viewer, so the floating
   button is pure duplication there. It is also the one place it does harm: it
   is fixed to the bottom-right, which is exactly where each card's "Release
   line" button sits, so a control you can see is a control you cannot press. */
body[data-tab="activity"] .fab,
body[data-tab="activity"] .fab-panel,
body[data-tab="activity"] .fab-backdrop { display: none; }
/* The Desk keeps the button on a computer: Adam looks for it there. It
   floats over a card at most scroll positions, as on every tab; main's
   bottom padding leaves room to scroll the last row out from under it. */
/* mcdPortal: every site row ends in action buttons, and the button sat on
   the last row's Delete. */
body[data-tab="mcdportal"] .fab,
body[data-tab="mcdportal"] .fab-panel,
body[data-tab="mcdportal"] .fab-backdrop { display: none; }
/* Homelab: the button sat on the tiles and says nothing about the homelab. */
body[data-tab="homelab"] .fab,
body[data-tab="homelab"] .fab-panel,
body[data-tab="homelab"] .fab-backdrop { display: none; }
/* Phones: the button sat on SiriusXM's stars and Settings' fields. The Desk
   strip and the Activity tab already say who is watching. */
@media (max-width: 700px) {
  .fab, .fab-panel, .fab-backdrop { display: none; }
}
/* A phone on its side is under 500px tall, and the button covered a card's
   buttons there too. */
@media (max-height: 500px) and (pointer: coarse) {
  .fab, .fab-panel, .fab-backdrop { display: none; }
}
.fab-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--muted-2); flex: 0 0 auto;
}
.fab-dot.live { background: var(--ok); animation: pulse 2s ease-in-out infinite; }
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(63, 207, 142, .55); }
  70% { box-shadow: 0 0 0 7px rgba(63, 207, 142, 0); }
}
.fab-count {
  min-width: 22px; padding: 1px 7px; border-radius: 999px;
  background: var(--bg-2); color: var(--muted);
  font-size: 12.5px; font-weight: 640; text-align: center;
}
.fab-count.live { background: var(--accent-dim); color: #eaf4ff; }

.fab-backdrop { position: fixed; inset: 0; z-index: 71; background: rgba(0,0,0,.4); }
.fab-panel {
  position: fixed; right: 18px; bottom: 74px; z-index: 72;
  width: min(420px, calc(100vw - 36px));
  max-height: min(70vh, 640px); overflow-y: auto;
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); box-shadow: var(--shadow);
}
@media (max-width: 560px) {
  .fab-panel {
    right: 0; left: 0; bottom: 0; width: 100vw;
    max-height: 78vh; border-radius: var(--r-lg) var(--r-lg) 0 0;
  }
  .fab { right: 14px; bottom: 14px; }
}
/* Only needed where the sheet covers the button that opened it. */
.fab-close-row { display: none; }
@media (max-width: 560px) {
  .fab-close-row {
    display: flex; justify-content: flex-end;
    padding: 10px 15px 0;
    position: sticky; top: 0; z-index: 1;
    background: var(--card);
  }
}
.fab-group { padding: 13px 15px; }
.fab-group + .fab-group { border-top: 1px solid var(--line); }
.fab-group-head {
  display: flex; align-items: center; gap: 8px; margin-bottom: 10px;
  font-size: 12px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted-2); font-weight: 620;
}
.fab-item { display: flex; gap: 10px; padding: 8px 0; align-items: flex-start; min-width: 0; }
.fab-item > * { min-width: 0; }
.fab-item + .fab-item { border-top: 1px solid var(--line); }
.fab-foot {
  display: flex; gap: 8px; padding: 11px 15px;
  border-top: 1px solid var(--line); background: var(--bg-2);
  position: sticky; bottom: 0;
}
.fab-foot button { flex: 1; font-size: 13px; min-height: 34px; }

/* ---------- utility ---------- */

/* Visually hidden but still announced. The portal had no h1 on any view. */
.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;
}
.stack { display: grid; gap: 12px; }
.stack-sm { display: grid; gap: 6px; }
/* Grid items default to min-width:auto, so they refuse to shrink below their
   content. One wide table therefore stretched its whole ancestor chain and
   pushed the document to 961px inside a 412px viewport: the browser zoomed the
   page out, and the stretched row meant the Activity sub-tabs stopped wrapping
   and "Logs" left the screen. .table-wrap could not help, because it was being
   handed 911px to fill. Same trap nav#tabs hit as a flex child. Constraining
   the items lets the wrapper scroll the table instead of the page. */
.stack > *, .stack-sm > * { min-width: 0; }
.wrap-anywhere { overflow-wrap: anywhere; }
.nowrap { white-space: nowrap; }
/* One part of a " · " line: kept whole, and cut with an ellipsis only when
   it is wider than the line by itself. */
.part { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
.right { text-align: right; }
.center { text-align: center; }
.grow { flex: 1 1 auto; min-width: 0; }
/* No display value above 700px. It used to set "initial", which computes to
   inline: on a <td> that dropped the cell out of the table's columns, so the
   Activity tables' headers stopped lining up with their cells. */
@media (max-width: 700px) { .hide-sm { display: none !important; } }

/* ---------- touch ---------- */

/* Real touch targets on coarse pointers. */
@media (pointer: coarse) {

  /* 44px is the smallest target both Apple and Google call reliably
     tappable. */
  nav#tabs button { min-height: 44px; }
  .seg button { min-height: 44px; padding: 8px 14px; }
  /* A bare <summary> collapses to its line box, so "Recent runs" was a 19px
     tall control. Make it a proper row without changing how it reads. */
  summary { min-height: 44px; display: flex; align-items: center; gap: 6px; }
  /* As a flex row a summary loses the browser's triangle, and "Recent runs"
     read as a heading over nothing. Settings' two plain disclosures get a
     chevron back; the others draw their own icon, and a Scores play row is
     a disclosure that must not grow one. */
  .runs-fold > summary::before, .sp-other > summary::before {
    content: ""; flex: none; width: 7px; height: 7px; margin: 0 5px 2px 3px;
    border: solid currentColor; border-width: 0 2px 2px 0; transform: rotate(-45deg);
    transition: transform .2s var(--ease-out);
  }
  .runs-fold[open] > summary::before, .sp-other[open] > summary::before { transform: rotate(45deg); margin-bottom: 4px; }
  /* 11px counts are legible on a desk monitor, not in a driveway. */
  .rail-groups .count { font-size: 12.5px; }
  /* Compact to a dot and a count so it intrudes less on the tabs that do keep
     it. The button carries aria-label="Now playing", so the name survives. */
  .fab-label { display: none; }

  /* Everywhere else a finger lands. Swept across ten form factors: the rail's
     43 channel-group rows were 34px, the primary buttons and text inputs
     38-40px, the favourite star 38px and the home link 30px. The rail gets
     taller, which costs scrolling on a list that is already scrolled, and is
     the right trade against mis-taps on a moving vehicle or a dark room. */
  button, .btn { min-height: 44px; }
  /* Needs its own rule: `.rail-groups button` is more specific than `button`,
     and a media query adds no specificity, so the 34px base rule wins without
     this. Under 900px the rail is a horizontal strip, so this costs height
     rather than scroll length. */
  .rail-groups button { min-height: 44px; }
  /* Same specificity problem as the rail. button.sm is the compact variant used
     for row actions like "Release line", which is the one control on Activity
     that actually does something irreversible, so it is the last one that
     should be a 30px target. */
  button.sm { min-height: 44px; }
  input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
  select, textarea { min-height: 44px; }
  .brand { min-height: 44px; }
}


/* ---------- desk ---------- */

/* minmax(0, 1fr), not the implicit auto track: auto grows to the cameras'
   natural image width and pushed the page to 1,800px on a phone. */
.desk { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr); }

.desk-top {
  display: flex; align-items: center; gap: 8px 14px; flex-wrap: wrap;
  padding: 10px 14px; background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r);
}
.desk-health { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; min-width: 0; flex: 1 1 auto; }
.desk-meta { display: flex; align-items: center; gap: 14px; margin-left: auto; min-width: 0; }
.desk-meta .desk-weather { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* On a phone the dots become an even two-column grid (a wrapped row left one
   item alone on its last line) and weather + clock share one row. */
@media (max-width: 560px) {
  /* nowrap: a wrapping column sizes each line to its widest item, so a long
     weather line pushed the whole strip past the screen. */
  .desk-top { flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 10px; }
  .desk-meta .desk-weather { white-space: normal; line-height: 1.45; }
  /* 6px gaps and slimmer icon chips (below) keep the strip to one row on a
     411px phone: it needed 358px of 349. */
  .desk-health { gap: 6px; }
  .desk-health .hdot.bad { white-space: normal; line-height: 1.3; }
  .desk-meta { margin-left: 0; justify-content: space-between; border-top: 1px solid var(--line); padding-top: 9px; }
}
.hdot { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; white-space: nowrap; position: relative; }
.hdot .icon { color: var(--muted); }
.hdot.ok .icon { color: var(--ok); }
.hdot.bad { color: var(--bad); font-weight: 600; padding: 3px 9px 3px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--bad) 14%, transparent); --tone: var(--bad); }
.hdot.bad .icon { color: var(--bad); }
.hdot-detail { font-weight: 400; color: color-mix(in srgb, var(--bad) 75%, var(--fg)); }
/* A chip that opens something: the "All 7 OK" fold and the watching count. */
.hchip {
  display: inline-flex; align-items: center; gap: 6px; min-height: 28px; padding: 3px 10px 3px 8px;
  border-radius: 999px; border: 1px solid var(--line-2); background: transparent;
  font-size: 13px; font-weight: 560; color: var(--muted); white-space: nowrap; position: relative;
}
/* Mouse only: on a phone a tapped chip kept the hover look. */
@media (hover: hover) and (pointer: fine) {
  .hchip:hover { background: var(--card-2); color: var(--fg); }
}
.hchip.t-ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, transparent);
  background: color-mix(in srgb, var(--ok) 10%, transparent); }
.hchip.t-on { color: var(--fg); border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent); }
.hchip.t-warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent);
  background: color-mix(in srgb, var(--warn) 11%, transparent); }
.hchip.t-bad { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 45%, transparent);
  background: color-mix(in srgb, var(--bad) 12%, transparent); }
.hchip.t-on .icon { color: var(--accent); }
/* The house at a glance: icon chips, a number where there is one. */
.hchip.glance { padding: 3px 9px; gap: 5px; font-weight: 700; }
.hchip.glance.t-idle { color: var(--muted); }
@media (max-width: 560px) { .hchip.glance { padding: 3px 7px; } }
.hchip-icon { position: relative; display: inline-grid; place-items: center; }
.hchip.t-on .hchip-icon { color: var(--accent); }
.hchip .live-dot { right: -4px; bottom: -2px; }
.hchip-count {
  min-width: 20px; padding: 0 6px; border-radius: 999px; text-align: center;
  background: var(--bg-2); color: var(--muted); font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.hchip-count.live { background: var(--accent-dim); color: #eaf4ff; }
/* A narrow phone keeps the strip to one row: Now playing is its icon and
   count, like the chips beside it (the label stays for screen readers). */
@media (max-width: 419px) {
  .desk-watching .hchip-text {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
}
.desk-weather { display: inline-flex; align-items: center; gap: 10px; font-size: 13.5px; color: var(--muted); white-space: nowrap; }
.wx-icon { display: inline-grid; width: 44px; height: 44px; margin: -8px -6px -8px -8px; flex: 0 0 auto; }
.wx-icon svg { width: 100%; height: 100%; }
.wx-temp { font-size: 20px; font-weight: 700; color: var(--fg); font-variant-numeric: tabular-nums; }
.wx-hl { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.desk-weather .wx-part { display: inline-flex; align-items: center; gap: 3px; }
.wx-raining { color: var(--accent); font-weight: 600; }
/* Looks like the other health items; it is a button because it opens the
   live viewer list. */
.desk-watching { text-align: left; }
.desk-clock { font-size: 15px; font-weight: 620; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* A phone's status bar shows the time already; the strip's room goes to
   the weather. The kitchen tablet and a desk monitor keep the clock. */
@media (max-width: 559px) { .desk-clock { display: none; } }

/* Two columns from 1100px: cameras, TV and events on the left (8/12), the
   house, controls, report, alerts and links on the right (4/12). Below that
   the columns dissolve (display: contents) and each section's inline order
   sets one list: health, status, controls, cameras, TV, events, report,
   alerts, links. */
.desk-cols {
  display: grid; gap: 16px;
  grid-template-columns: minmax(0, 8fr) minmax(0, 4fr);
  align-items: start;
}
.desk-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* Wide monitors: the Desk takes the width, and the side column becomes two
   (three on the widest) newspaper columns of cards. */
@media (min-width: 1800px) {
  body[data-tab="desk"] main { max-width: 2600px; }
  .desk-cols { grid-template-columns: minmax(0, 7fr) minmax(0, 8fr); }
  .desk-side { display: block; column-count: 2; column-gap: 16px; }
  .desk-side > .desk-sec { break-inside: avoid; margin-bottom: 16px; }
}
@media (min-width: 2500px) {
  .desk-cols { grid-template-columns: minmax(0, 6fr) minmax(0, 10fr); }
  .desk-side { column-count: 3; }
}
/* A tablet, or the Fold opened (about 770 to 880px), keeps two columns: as
   one list, most of each card sat empty at that width. The cameras column
   takes a little more. */
@media (min-width: 720px) and (max-width: 1099px) {
  .desk-cols { grid-template-columns: minmax(0, 11fr) minmax(0, 9fr); gap: 14px; }
  .desk-col { gap: 14px; }
  /* In that column the cameras' View all and Home | Parents fill a row of
     their own, so the fold chevron stays beside the title instead of
     wrapping alone. */
  #desk-cams .section-head .sec-controls { order: 5; flex: 1 0 100%; }
}
@media (max-width: 719px) {
  /* stretch, or the start alignment from the wide grid shrink-wraps every
     card to its content. */
  .desk-cols { display: flex; flex-direction: column; align-items: stretch; }
  .desk-col { display: contents; }
}
.desk-sec { min-width: 0; padding: 14px; }
/* A folded card is its title row; the chevron turns to say it opens. */
.sec-fold {
  flex: 0 0 auto; width: 32px; height: 32px; min-height: 32px; margin: -4px -6px -4px 2px; padding: 0;
  display: grid; place-items: center; border: 0; border-radius: 50%; background: transparent; color: var(--muted);
}
@media (hover: hover) and (pointer: fine) {
  .sec-fold:hover { background: var(--card-2); color: var(--fg); }
}
.sec-fold .icon { transition: transform .2s var(--ease-out); }
.desk-sec.folded .sec-fold .icon { transform: rotate(-90deg); }
.desk-sec.folded > .sec-body, .desk-sec.folded .sec-controls { display: none; }
.desk-sec.folded .section-head { margin-bottom: 0; }
@media (pointer: coarse) { .sec-fold { width: 44px; height: 44px; min-height: 44px; margin: -8px -10px -8px 0; } }
/* A control in a card head (the Home | Parents switch, the report's day)
   drops below the title as one piece rather than splitting its buttons. */
.desk-sec .section-head { margin-bottom: 10px; flex-wrap: wrap; row-gap: 8px; align-items: center; }
.desk-sec .section-head .seg { flex-wrap: nowrap; }

.desk-banner {
  font-size: 13px; padding: 8px 11px; border-radius: var(--r-sm); margin-bottom: 10px;
  border: 1px solid var(--line-2); background: var(--card-2);
}
.desk-banner.warn { color: var(--warn); border-color: #5c4a1d; background: #241f10; }

/* ---------- glance kit: badges, rings, avatars, live dots ---------- */
.t-ok { --tone: var(--ok); }
.t-warn { --tone: var(--warn); }
.t-bad { --tone: var(--bad); }
.t-on { --tone: var(--accent); }
.t-idle { --tone: var(--muted); }
.badge {
  --bs: 32px; position: relative; display: inline-grid; place-items: center; flex: 0 0 auto;
  width: var(--bs); height: var(--bs); border-radius: 50%;
  color: var(--tone); background: color-mix(in srgb, var(--tone) 16%, transparent);
}
.badge .icon { display: grid; }
.badge-count {
  position: absolute; top: -4px; right: -6px; min-width: 17px; height: 17px; padding: 0 4px;
  display: grid; place-items: center; border-radius: 9px; border: 2px solid var(--card);
  background: var(--tone); color: #0b0e14; font-size: 10.5px; font-weight: 760; font-variant-numeric: tabular-nums;
}
/* Something happening now. The ring is transform and opacity only. */
.live-dot {
  position: absolute; right: -1px; bottom: -1px; width: 10px; height: 10px; border-radius: 50%;
  background: var(--ok); border: 2px solid var(--card);
}
.live-dot::after {
  content: ""; position: absolute; inset: -2px; border-radius: 50%; border: 2px solid var(--ok);
  animation: live-ring 2s var(--ease-out) infinite;
}
@keyframes live-ring { from { transform: scale(1); opacity: .6; } to { transform: scale(2.4); opacity: 0; } }
.ring { position: relative; display: inline-grid; place-items: center; width: var(--rs); height: var(--rs); flex: 0 0 auto; }
.ring svg { position: absolute; inset: 0; transform: rotate(-90deg); }
.ring circle { fill: none; stroke-width: 3.6; }
.ring-bg { stroke: var(--line-2); }
.ring-fg { stroke: var(--tone); stroke-linecap: round; }
.ring-val { font-size: calc(var(--rs) * .27); font-weight: 720; font-variant-numeric: tabular-nums; }
.avatar {
  width: 30px; height: 30px; border-radius: 50%; display: inline-grid; place-items: center; flex: 0 0 auto;
  font-size: 11.5px; font-weight: 760; letter-spacing: .02em;
  background: var(--card-2); color: var(--fg); box-shadow: 0 0 0 2px var(--tone);
}
/* A real change (a door opening, someone arriving) pops its icon and
   washes the tile in its color once. Never on a plain redraw. */
.chg.badge, .chg .badge, .chg .avatar, .chg .icon { animation: glance-pop 240ms var(--ease-out); }
.stile.chg::after, .person.chg::after, .dbtn:has(.chg)::after, .hdot.chg::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: var(--tone, var(--accent)); opacity: 0; animation: glance-glow 1200ms ease-out;
}
.dbtn:has(.chg) { --tone: var(--accent); }
@keyframes glance-pop { from { transform: scale(.82); opacity: .4; } to { transform: none; opacity: 1; } }
@keyframes glance-glow { from { opacity: .22; } to { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .chg.badge, .chg .badge, .chg .avatar, .chg .icon, .live-dot::after { animation: none; }
}

/* Card heads carry an icon. */
.desk-sec .sec-title { display: flex; align-items: center; gap: 8px; }
.desk-sec .sec-title .icon { display: grid; color: var(--muted); }
#desk-attention .sec-title .icon { color: var(--warn); }
.t-plex { --tone: var(--plex); }
/* A card that appears (Needs attention) slides in once. */
.desk-sec.appear { animation: card-in 240ms var(--ease-out); }
@keyframes card-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .desk-sec.appear { animation: none; } }

/* Needs attention, Today: a badge leads each row. */
.attn-ack {
  flex: 0 0 auto; margin-left: auto; width: 36px; height: 36px; min-height: 36px; padding: 0;
  display: grid; place-items: center; border-radius: 50%; border: 1px solid var(--line-2);
  background: transparent; color: var(--muted);
}
@media (hover: hover) and (pointer: fine) {
  .attn-ack:hover { color: var(--ok); border-color: var(--ok); }
}
@media (pointer: coarse) { .attn-ack { width: 44px; height: 44px; min-height: 44px; } }
.attn-hidden { display: flex; align-items: center; gap: 8px; margin-top: 8px; }

/* Games: team logos on a light disc, Live with a live dot, results as W
   or L. */
.team-logo {
  --ls: 24px; display: inline-grid; place-items: center; flex: 0 0 auto; vertical-align: middle;
  width: var(--ls); height: var(--ls); border-radius: 50%; background: rgba(255, 255, 255, .94);
  margin-right: 6px; box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
}
.team-logo img { width: 78%; height: 78%; object-fit: contain; }
.wl { display: inline-flex; align-items: baseline; gap: 2px; color: var(--tone); }
.wl strong { display: inline-grid; place-items: center; min-width: 20px; height: 20px; border-radius: 5px;
  background: color-mix(in srgb, var(--tone) 18%, transparent); font-size: 12px; }

/* Network: the link as a badge, the facts as icon chips. */
.net-link { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.net-link .net-facts { display: flex; gap: 6px 12px; flex-wrap: wrap; margin-left: auto; }
/* Wrapped under the name on a phone, the facts start where the name does
   rather than hanging off the right edge. */
@media (max-width: 559px) { .net-link .net-facts { flex: 1 0 100%; margin-left: 0; padding-left: 42px; } }
.net-fact { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: var(--fg); font-variant-numeric: tabular-nums; }
.net-fact .icon { color: var(--muted); }
.net-flag { display: inline-flex; align-items: center; gap: 4px; }

/* Sites: all up folds to one line. */
.sites-fold > summary { display: flex; align-items: center; gap: 10px; cursor: pointer; list-style: none; min-height: 40px; }
.sites-fold > summary::-webkit-details-marker { display: none; }
.sites-fold[open] > summary { margin-bottom: 8px; }
.site-row { align-items: center; }
.site-row > .icon { color: var(--ok); display: grid; }
.site-row.warn > .icon { color: var(--warn); }
.site-row.bad > .icon { color: var(--bad); }
.site-row .pdot { display: none; }

/* Power: the numbers lead with an icon. */
.power-rate { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.power-val { display: inline-flex; align-items: center; gap: 5px; }
.power-val .icon { color: var(--accent); }

/* Where the car is: a dark map, the place big, the street under it. */
.car-where { margin-bottom: 14px; }
.car-map {
  position: relative; display: block; height: 150px; overflow: hidden; border-radius: var(--r-sm);
  background: #1a1f29; border: 1px solid var(--line); margin-bottom: 10px;
}
.map-tile {
  position: absolute; width: 256px; height: 256px; max-width: none; user-select: none;
  filter: invert(1) hue-rotate(180deg) brightness(.9) contrast(.88) saturate(.55);
}
.car-pin {
  position: absolute; left: 50%; top: 50%; width: 34px; height: 34px; margin: -17px 0 0 -17px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--accent); color: #07111f; box-shadow: 0 0 0 4px rgba(90, 169, 255, .28), 0 4px 12px rgba(0, 0, 0, .5);
}
.car-pin .icon { display: grid; }
.car-pin.moving::after {
  content: ""; position: absolute; inset: -4px; border-radius: 50%; border: 2px solid var(--accent);
  animation: live-ring 2s var(--ease-out) infinite;
}
@media (prefers-reduced-motion: reduce) { .car-pin.moving::after { animation: none; } }
.map-credit {
  position: absolute; right: 4px; bottom: 3px; font-size: 9.5px; color: rgba(232, 238, 247, .7);
  background: rgba(11, 14, 20, .6); padding: 0 4px; border-radius: 3px;
}
.car-place-line { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.car-place { font-size: 19px; font-weight: 720; }
.car-place-detail { font-size: 13px; color: var(--muted); }
.car-place-detail.moving { color: var(--ok); font-weight: 600; }
.car-address { display: flex; align-items: center; gap: 4px; font-size: 13px; margin-top: 3px; }

/* The car: fuel as a ring, the facts as icons. */
.car-range { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; }
.car-engine { display: inline-flex; align-items: center; gap: 5px; color: var(--ok); font-size: 13px; font-weight: 600; position: relative; padding-left: 14px; }
.car-engine .live-dot { left: 0; right: auto; bottom: auto; top: 50%; margin-top: -5px; border-color: transparent; }
.car-tire { position: relative; }
.car-tire.low .icon { color: var(--bad); }
.car-actions button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }

/* Cameras today: an icon and a count for each thing seen. */
.seen-list { display: inline-flex; gap: 8px; }
.seen { display: inline-flex; align-items: center; gap: 2px; font-variant-numeric: tabular-nums; }
.seen .icon { color: var(--muted); }

/* Media heads. */
.music-head { display: flex; align-items: center; gap: 5px; }

/* Links: the service's icon, colored by whether it answers. */
.dlink-icon { display: grid; color: var(--tone); flex: 0 0 auto; }
.dlink .ldot { display: none; }

/* Music: an equalizer on the playing station. scaleY only. */
.music-art { position: relative; display: inline-grid; flex: 0 0 auto; }
.eq {
  position: absolute; right: 3px; bottom: 3px; display: flex; align-items: flex-end; gap: 2px;
  height: 14px; padding: 2px; border-radius: 4px; background: rgba(8, 11, 16, .72);
}
.eq i { width: 3px; height: 10px; background: var(--ok); border-radius: 1px; transform-origin: bottom; animation: eq 1s ease-in-out infinite alternate; }
.eq i:nth-child(2) { animation-duration: .9s; animation-delay: -.3s; }
.eq i:nth-child(3) { animation-duration: 1.1s; animation-delay: -.6s; }
@keyframes eq { from { transform: scaleY(.3); } to { transform: scaleY(1); } }
@media (prefers-reduced-motion: reduce) { .eq i { animation: none; transform: scaleY(.7); } }

/* status tiles */
.stiles { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
.stiles.stale { opacity: .55; }
.stile {
  display: flex; align-items: center; gap: 10px; position: relative;
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 8px 10px; min-width: 0;
}
.stile-text { min-width: 0; }
.stile.t-warn { border-color: color-mix(in srgb, var(--warn) 40%, var(--line)); }
.stile.t-bad { border-color: color-mix(in srgb, var(--bad) 40%, var(--line)); }
.stile.t-warn .stile-value { color: var(--warn); }
.stile.t-bad .stile-value { color: var(--bad); }
.stile-label { font-size: 12px; color: var(--muted); }
.stile-value { font-size: 15px; font-weight: 650; }
/* The Home card's head: "Secure" or "2 open". */
.head-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px 3px 8px; border-radius: 999px;
  font-size: 12.5px; font-weight: 650; color: var(--tone);
  background: color-mix(in srgb, var(--tone) 14%, transparent);
}

/* People: one row each. The dot is green at home, blue in a named zone (a
   school, daycare), grey away, amber when the tracker has nothing. */
.people { display: flex; flex-direction: column; }
.people.stale { opacity: .55; }
.person {
  display: grid; grid-template-columns: 30px 74px minmax(0, 1fr); align-items: center;
  column-gap: 10px; padding: 6px 2px; border-bottom: 1px solid var(--line); font-size: 14px; position: relative;
}
.person:last-child { border-bottom: 0; }
.person-name { font-weight: 600; }
.person-where { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; min-width: 0; }
/* A long place ("Epworth Elementary") shortens with an ellipsis rather than
   pushing a 320px phone wide. */
.place-chip {
  display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px 2px 6px; border-radius: 999px;
  color: var(--tone); background: color-mix(in srgb, var(--tone) 13%, transparent); white-space: nowrap;
  min-width: 0; max-width: 100%;
}
.place-chip .icon { flex: none; }
.person-place { font-weight: 650; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.person.t-idle .place-chip { color: var(--fg); }
.person-sub { font-size: 12.5px; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.person-sub.stale-chip { display: inline-flex; align-items: center; color: var(--warn); }
/* The day so far, under the place: whole stops, wrapping between them. */
.person-today { grid-column: 2 / -1; font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.person-stop { white-space: nowrap; }
.person-arrow { color: var(--muted-2); }

/* control buttons */
.dbtns { display: grid; gap: 8px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.dbtn {
  display: flex; flex-direction: row; align-items: center; gap: 10px;
  text-align: left; min-height: 56px; padding: 8px 10px; min-width: 0; position: relative;
}
.dbtn-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.dbtn-label { font-weight: 600; font-size: 14px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dbtn-state { font-size: 12px; color: var(--muted); }
.dbtn.active { border-color: var(--accent); background: #101f30; }
.dbtn.active .dbtn-state { color: var(--accent); }

/* A garage door's panel, like Home Assistant's more-info dialog: the state,
   a position bar, and Close, Stop and Open. */
.cover-modal { min-width: 0; }
/* The modal card is sized for live video; a door's panel needs far less. */
.modal-card:has(.cover-modal) { width: min(440px, 100%); }
.cover-state { font-size: 20px; font-weight: 650; margin: 2px 0 14px; }
.cover-slider { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
/* A light that is off: its slider steps back, so a thumb resting at 0%
   does not read as a light a little on. */
.group-row:has(.switch[aria-checked="false"]) .cover-slider { opacity: .45; }
.cover-val {
  flex: 0 0 auto; width: 44px; text-align: right;
  font-size: 14px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.cover-btns { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.cover-btn { min-height: 44px; }
.cover-btns.two { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-bottom: 6px; }
.cover-btns.one { grid-template-columns: minmax(0, 1fr); margin-bottom: 6px; }

/* A switch for a light: a pill with a knob, on to the right. */
.switch {
  position: relative; width: 52px; min-width: 52px; height: 30px; min-height: 30px; padding: 0;
  border-radius: 999px; background: var(--bg-2); border: 1px solid var(--line-2);
}
.switch i {
  position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 50%;
  background: var(--muted); transition: left .15s, background .15s;
}
.switch[aria-checked="true"] { background: var(--accent-dim); border-color: var(--accent); }
.switch[aria-checked="true"] i { left: 25px; background: #fff; }
@media (hover: hover) and (pointer: fine) {
  .switch:hover { background: var(--bg-2); }
}
@media (hover: hover) and (pointer: fine) {
  .switch[aria-checked="true"]:hover { background: var(--accent-dim); }
}
@media (pointer: coarse) { .switch { height: 44px; min-height: 44px; width: 64px; } .switch i { top: 5px; width: 32px; height: 32px; } .switch[aria-checked="true"] i { left: 27px; } }
@media (prefers-reduced-motion: reduce) { .switch i { transition: none; } }

/* The thermostat's panel. */
.cl-head { display: flex; align-items: baseline; gap: 12px; margin: 4px 0 14px; }
.cl-head-label { display: block; font-size: 12px; color: var(--muted); }
.cl-now { font-size: 40px; font-weight: 650; line-height: 1; font-variant-numeric: tabular-nums; }
.cl-targets { display: flex; flex-direction: column; gap: 10px; margin-bottom: 14px; }
.cl-stepper { display: flex; align-items: center; gap: 12px; }
.cl-step-label { width: 64px; }
.cl-btn { width: 52px; height: 52px; min-height: 52px; padding: 0; border-radius: 50%; font-size: 24px; line-height: 1; }
.cl-target { min-width: 64px; text-align: center; font-size: 28px; font-weight: 650; font-variant-numeric: tabular-nums; }
.cl-modes { display: flex; width: 100%; }
.cl-modes button { flex: 1 1 0; min-height: 44px; }

/* Weather alerts, under the health strip. */
.wx-alerts { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 6px; }
.wx-alert {
  display: flex; flex-wrap: wrap; gap: 4px; align-items: baseline; text-align: left; width: 100%;
  font-size: 13.5px; padding: 8px 12px; min-height: 40px; border-radius: var(--r-sm);
}
.wx-alert.warn { color: var(--warn); border-color: #5c4a1d; background: #241f10; }
.wx-alert.bad { color: var(--bad); border-color: #6b2b30; background: #2a1418; }
.wx-instruction { font-weight: 600; }
.wx-text { white-space: pre-line; color: var(--muted); }

/* Banners in the top strip take a full line of their own. Only in the row
   layout: below 561px the strip is a column, where 100% is a height. */
@media (min-width: 561px) { .desk-top > .wx-alert, .desk-top > .wx-alerts { flex: 1 1 100%; } }
.wx-part { white-space: nowrap; }

/* Today: school, daycare, trash, holidays. Laid out like People. */
.today { display: flex; flex-direction: column; }
.today-row {
  display: grid; grid-template-columns: 28px 72px minmax(0, 1fr); align-items: center;
  column-gap: 10px; padding: 6px 2px; border-bottom: 1px solid var(--line); font-size: 14px;
}
.today-row:last-child { border-bottom: 0; }
.today-row.ok .pdot { background: var(--ok); }
.today-row.warn .pdot { background: var(--warn); }
.today-row.warn .today-title { color: var(--warn); }
.today-who { font-weight: 600; }
.today-title { font-weight: 600; }
.today-detail, .today-tomorrow { display: block; font-size: 12.5px; color: var(--muted); }
.today-tomorrow { color: var(--warn); }

/* Games: a scoreboard tile for each followed team (road team left, as on a
   TV score bug), then the prime-time games one per row. */
.gm-team { padding: 2px 0 12px; }
.gm-team + .gm-team, .game-prime { border-top: 1px solid var(--line); padding-top: 12px; }
.gm-head { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.gm-label { text-transform: uppercase; letter-spacing: .05em; font-weight: 700; margin-bottom: 6px; }
.gm-board { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 8px; }
.gm-side { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 0; text-align: center; }
.gm-side .team-logo, .gp-side .team-logo, .gm-head .team-logo { margin-right: 0; }
.gm-name { max-width: 100%; font-size: 13px; font-weight: 600; color: var(--muted); }
.gm-side.me .gm-name { color: var(--fg); font-weight: 700; }
.gm-score { font-size: 28px; font-weight: 780; line-height: 1; font-variant-numeric: tabular-nums; }
.gm-side.win .gm-score { color: var(--ok); }
.gm-mid { display: flex; flex-direction: column; align-items: center; gap: 3px; min-width: 88px; }
.gm-at { font-size: 11px; font-weight: 700; color: var(--muted-2); }
.gm-day { font-size: 11.5px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; white-space: nowrap; }
.gm-time { font-size: 19px; font-weight: 760; white-space: nowrap; font-variant-numeric: tabular-nums; }
.gm-tv {
  font-size: 10.5px; font-weight: 760; padding: 1px 8px; border-radius: 999px;
  color: var(--muted); background: var(--card-2); border: 1px solid var(--line-2);
}
.gm-live {
  display: inline-flex; align-items: center; gap: 6px; padding: 1px 8px 1px 6px; border-radius: 999px;
  font-size: 11px; font-weight: 700; color: var(--bad); background: #2a1418; border: 1px solid #6b2b30;
}
.gm-live .live-dot { position: relative; right: auto; bottom: auto; border-color: transparent; background: var(--bad); }
.gm-live .live-dot::after { border-color: var(--bad); }
.gm-detail { font-size: 13px; font-weight: 700; white-space: nowrap; color: var(--ok); }
.gm-last { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 10px; font-size: 12.5px; min-width: 0; }
.gm-listen-row { display: flex; justify-content: center; margin-top: 10px; }
.gm-listen { display: inline-flex; align-items: center; gap: 6px; }
.gm-listen.playing { color: #fff; background: color-mix(in srgb, #1db954 45%, var(--card-2)); border-color: color-mix(in srgb, #1db954 70%, var(--line)); }
.gp-row {
  /* A fixed status column keeps the @ in line from row to row. */
  display: grid; grid-template-columns: 34px minmax(0, 1fr) 14px minmax(0, 1fr) 72px; column-gap: 8px;
  align-items: center; padding: 6px 0; font-size: 13.5px;
}
.gp-row + .gp-row { border-top: 1px solid color-mix(in srgb, var(--line) 60%, transparent); }
.gp-day { font-size: 11px; font-weight: 760; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.gp-side { display: flex; align-items: center; gap: 6px; min-width: 0; font-weight: 700; }
.gp-side.lost { color: var(--muted); font-weight: 550; }
.gp-score { margin-left: auto; font-weight: 780; font-variant-numeric: tabular-nums; }
.gp-at { font-size: 11px; color: var(--muted-2); text-align: center; }
.gp-status { font-size: 12px; color: var(--muted); white-space: nowrap; text-align: right; }
.gp-status.live, .gp-row.live .gp-side { color: var(--ok); }

/* The 3D printer, while it prints. */
.printer-cam {
  display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: var(--r-sm); background: #000; margin-bottom: 10px;
}
.printer-line { font-size: 13.5px; margin-top: 4px; }
.printer-progress { margin: 6px 0 2px; }
.printer-filament { display: flex; align-items: center; gap: 6px; }
.swatch { width: 12px; height: 12px; border-radius: 50%; border: 1px solid var(--line-2); flex: 0 0 auto; }

/* The grill, while it runs: the grill and each probe against its target. */
.grill-head { display: flex; align-items: center; gap: 10px; min-width: 0; }
.grill-head strong { font-size: 15px; }
.grill-temps { display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); gap: 10px; margin-top: 12px; }
.grill-temp { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 10px 12px; min-width: 0; }
.gt-label { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--muted); }
.gt-now { font-size: 30px; font-weight: 760; line-height: 1.15; font-variant-numeric: tabular-nums; }
.gt-target { font-size: 12.5px; color: var(--muted); }
.gt-bar { margin-top: 8px; }
.grill-temp.close .gt-now { color: var(--warn); }
.grill-temp.done { border-color: color-mix(in srgb, var(--ok) 55%, transparent); }
.grill-temp.done .gt-now { color: var(--ok); }
.grill-temp.done .gt-bar > i { background: var(--ok); }
.grill-facts { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 10px; font-size: 13.5px; }
.grill-fact { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.grill-fact .icon { color: var(--muted); }
.grill-fact.low, .grill-fact.low .icon { color: var(--warn); }

/* Network and client sites. */
.net-link { font-size: 14px; }
.net-fact { white-space: nowrap; }
.net-link.ok .pdot { background: var(--ok); }
.net-link.bad .pdot { background: var(--bad); }
.net-link.bad strong { color: var(--bad); }
.net-link + .media-part { margin-top: 12px; }
.net-flag {
  flex: 0 0 auto; font-size: 11px; font-weight: 650; padding: 1px 8px; border-radius: 999px;
  color: var(--warn); background: #241f10; border: 1px solid #5c4a1d;
}
.site-row .pdot { margin-top: 0; align-self: center; }
.site-row.ok .pdot { background: var(--ok); }
.site-row.warn .pdot { background: var(--warn); }
.site-row.bad .pdot { background: var(--bad); }
.site-name { display: block; color: var(--fg); font-weight: 600; text-decoration: none; }
@media (hover: hover) and (pointer: fine) {
  .site-name:hover { text-decoration: underline; }
}
/* A 44px touch target without making the rows taller. */
@media (pointer: coarse) { .site-name { padding: 12px 0; margin: -12px 0; } }
.coming-more { padding: 6px 2px 0; }
.site-exposed { color: var(--bad); font-weight: 600; }

/* The speaker picker for the music card. */
.modal-card:has(.speaker-modal) { width: min(440px, 100%); }
.speaker-list { display: flex; flex-direction: column; gap: 6px; }
.speaker-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; min-height: 44px; text-align: left;
}

/* Needs attention: the worst first, a dot for how bad. */
.attn { display: flex; flex-direction: column; }
.attn-row {
  /* The third column is the acknowledge check, at the row's end. */
  display: grid; grid-template-columns: 32px minmax(0, 1fr) auto; column-gap: 10px; align-items: center;
  padding: 6px 2px; border-bottom: 1px solid var(--line); font-size: 14px;
}
.attn-row:last-child { border-bottom: 0; }
.attn-title { font-weight: 600; }
.attn-row.bad .attn-title { color: var(--bad); }
#desk-attention { border-color: #5c4a1d; }

/* Routines: two wide buttons above the controls. */
.scene-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-bottom: 8px; }
.scene-btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 48px; font-weight: 600; background: var(--accent-dim); border-color: var(--accent); color: #eaf4ff;
}
@media (hover: hover) and (pointer: fine) {
  .scene-btn:hover { background: #245b98; }
}
.modal-card:has(.scene-modal) { width: min(460px, 100%); }
.scene-list { display: flex; flex-direction: column; gap: 8px; margin: 10px 0 14px; }
.scene-change { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 10px; font-size: 14px; align-items: baseline; }
.scene-offline { color: var(--warn); }
.scene-actions { display: grid; }
.scene-actions .primary { min-height: 48px; }

/* Power. */
.power-rate {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 4px;
  padding: 9px 12px; border-radius: var(--r-sm); border: 1px solid var(--line-2); background: var(--bg-2); font-size: 14px;
}
.power-rate.peak { color: var(--warn); border-color: #5c4a1d; background: #241f10; }
.power-rate.off strong { color: var(--ok); }
.power-next { margin-left: auto; font-size: 12.5px; color: var(--muted); }
.power-rate.peak .power-next { color: var(--warn); }
.power-warn { margin: 10px 0 0; }
.power-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin: 12px 0 8px; }
.power-stat { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 8px 10px; min-width: 0; }
.power-val { font-size: 17px; font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The car. */
.car-fuel { display: flex; align-items: center; gap: 14px; }
.car-fuel-line { display: flex; align-items: center; gap: 5px; font-size: 13px; color: var(--muted); }
.car-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 12px 0 8px; }
.car-fact {
  display: flex; align-items: center; gap: 9px;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 7px 10px; min-width: 0;
}
.car-fact > div { min-width: 0; }
.car-fact.warn { border-color: color-mix(in srgb, var(--warn) 40%, var(--line)); }
.car-fact.warn .car-fact-val { color: var(--warn); }
.car-fact-val { font-weight: 600; font-size: 14.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.car-tires { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; margin-bottom: 8px; }
.car-psi { font-size: 11px; font-weight: 500; color: var(--muted); }
.car-tire { display: flex; flex-direction: column; align-items: center; padding: 6px 4px; border-radius: var(--r-sm); background: var(--bg-2); border: 1px solid var(--line); font-variant-numeric: tabular-nums; }
.car-tire.low { border-color: #6b2b30; }
.car-tire.low strong { color: var(--bad); }
.car-running { color: var(--accent); }
.car-actions { display: flex; gap: 8px; margin: 10px 0 6px; }
.car-actions button { flex: 1 1 0; min-height: 44px; }

/* Today on the cameras: chips under the grid. */
/* One swipeable row, like the events under it. */
.camday {
  display: flex; gap: 8px; margin-top: 12px; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}
.camday::-webkit-scrollbar { display: none; }
.camday-chip {
  flex: 0 0 auto; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 6px 12px;
  border-radius: 999px; font-size: 13px; background: var(--bg-2);
}
.camday-chip span { color: var(--muted); }
@media (pointer: coarse) { .camday-chip { min-height: 44px; } }

/* Rewind. */
.rw-stage { position: relative; background: #000; border-radius: var(--r-sm); overflow: hidden; aspect-ratio: 16 / 9; }
.rw-video, .rw-frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; }
.rw-none { position: absolute; inset: 0; display: grid; place-items: center; color: var(--muted); font-size: 14px; }
.rw-when {
  position: absolute; left: 8px; top: 8px; padding: 2px 9px; border-radius: 999px;
  background: rgba(0, 0, 0, .66); color: #fff; font-size: 12.5px; font-weight: 600; font-variant-numeric: tabular-nums;
}
.rw-track { position: relative; margin-top: 12px; }
.rw-range { width: 100%; margin: 0; height: 36px; accent-color: var(--accent); position: relative; z-index: 1; background: transparent; }
.rw-marks { position: absolute; left: 8px; right: 8px; top: 2px; height: 32px; pointer-events: none; }
.rw-mark { position: absolute; top: 4px; width: 3px; height: 24px; margin-left: -1px; border-radius: 2px; background: var(--warn); opacity: .9; }
.rw-mark.person { background: var(--accent); }
.rw-mark.car { background: var(--warn); }
.rw-mark.dog, .rw-mark.cat { background: var(--ok); }
.rw-controls { display: flex; align-items: center; gap: 8px; margin: 6px 0 10px; }
.rw-play { min-width: 96px; min-height: 44px; }
.rw-speed { min-height: 44px; min-width: 56px; }
.rw-events { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.rw-event { cursor: pointer; min-height: 32px; }
@media (pointer: coarse) { .rw-event { min-height: 44px; padding: 8px 12px; } }
@media (max-width: 600px) { .rw-modal .rw-stage { margin: 0 -14px; border-radius: 0; } }

/* Media card. */
.media-part + .media-part { margin-top: 12px; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip { font-size: 12.5px; padding: 3px 9px; border-radius: 999px; background: var(--bg-2); border: 1px solid var(--line); }
.cam-pop-replies { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 8px; }
@media (pointer: coarse) { .cam-pop-replies button { min-height: 44px; } }
/* The blinds panel: a row per blind, name and state over its bar. */
.group-list { display: flex; flex-direction: column; }
.group-row { padding: 10px 0 2px; border-top: 1px solid var(--line); }
.group-row-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
/* A light's name sits level with its switch. */
.group-row-head:has(.switch) { align-items: center; }
.group-name { flex: 1 1 auto; min-width: 0; font-weight: 600; }
.group-row .cover-slider { margin-bottom: 8px; }
.dslider {
  -webkit-appearance: none; appearance: none;
  display: block; flex: 1 1 auto; min-width: 0; width: auto; height: 44px; min-height: 0; padding: 0; margin: 0;
  border: 1px solid var(--line-2); border-radius: var(--r-sm); cursor: pointer;
  background: linear-gradient(to right, var(--accent-dim) var(--pos, 0%), var(--bg-2) var(--pos, 0%));
}
.dslider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 12px; height: 44px; border: 0; border-radius: 4px; background: var(--accent);
}
.dslider::-moz-range-thumb { width: 12px; height: 44px; border: 0; border-radius: 4px; background: var(--accent); }
.dslider:disabled { opacity: .45; cursor: not-allowed; }
.dslider:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* cameras */
.desk-cams { display: grid; gap: 8px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1099px) { .desk-cams { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.cam {
  position: relative; display: block; padding: 0; min-height: 0; overflow: hidden;
  aspect-ratio: 16 / 9; background: #000; border: 1px solid var(--line); border-radius: var(--r-sm);
}
@media (hover: hover) and (pointer: fine) {
  .cam:hover { background: #000; border-color: var(--accent); }
}
.cam img { display: block; width: 100%; height: 100%; object-fit: contain; }
.cam img:not([src]) { visibility: hidden; }
/* Labels sit at the top: every camera burns its own clock into the bottom
   corners. */
.cam-label, .cam-live {
  position: absolute; top: 6px; font-size: 12px; font-weight: 600;
  padding: 2px 8px; border-radius: 999px; background: rgba(0, 0, 0, .62); color: #fff;
}
.cam-label { left: 6px; }
.cam-live { right: 6px; opacity: 0; transition: opacity .12s; }
.cam:focus-visible .cam-live { opacity: 1; }
@media (hover: hover) and (pointer: fine) {
  .cam:hover .cam-live { opacity: 1; }
}
.cam-nopic { display: none; position: absolute; inset: 0; place-items: center; color: var(--muted); font-size: 13px; }
.cam.nopic .cam-nopic { display: grid; }
.cam.nopic img { visibility: hidden; }

.live-video {
  display: block; width: 100%; max-height: 70vh; background: #000; border-radius: var(--r-sm);
}
.live-modal .tiny:empty { display: none; }
/* The full stream loads on top of the substream, invisible but decoding,
   and appears on its first frame. */
.live-stage { position: relative; }
.live-next { position: absolute; inset: 0; height: 100%; opacity: 0; pointer-events: none; }
.live-next.ready { position: static; height: auto; opacity: 1; pointer-events: auto; }
/* On a phone the picture runs edge to edge inside the sheet. */
@media (max-width: 600px) {
  .live-modal .live-stage { margin: 0 -14px; }
  .live-modal .live-video { border-radius: 0; }
}
/* The poster is the tile's snapshot: fill the frame like the feed will. */
.live-video { object-fit: contain; }
/* A tap on the picture opens it full screen. */
.live-stage { cursor: zoom-in; }
.live-stage:fullscreen { background: #000; display: flex; align-items: center; justify-content: center; }
.live-stage:fullscreen video, .live-stage:fullscreen img { width: 100%; height: 100%; object-fit: contain; }
.live-modal .seg { padding: 2px; }
.live-head { gap: 6px; }
.live-step { width: 40px; height: 40px; min-height: 40px; padding: 0; display: grid; place-items: center; border-radius: 50%; }
.live-pos { font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (pointer: coarse) { .live-step { width: 44px; height: 44px; min-height: 44px; } }
/* A wide screen gets a bigger picture: the dialog was held to 760px. */
@media (min-width: 1100px) {
  .modal-card:has(.live-modal) { width: min(1280px, 100%); }
  .live-video { max-height: 74vh; }
}
.live-modal .seg button { min-height: 28px; padding: 3px 11px; }

/* ---------- the camera wall ---------- */
/* The card's button sits before the site switch. */
#desk-cams-controls { display: flex; align-items: center; gap: 8px; }
.cam-wall-open { display: inline-flex; align-items: center; gap: 6px; }
@media (max-width: 600px) { .cam-wall-open-text { display: none; } }
/* Below 400px the header has no room for it (the switch and the fold
   button fill it), so the button sits under the tiles instead. */
.cam-wall-row { display: none; width: 100%; margin-top: 8px; justify-content: center; align-items: center; gap: 8px; }
@media (max-width: 399px) {
  .cam-wall-open { display: none; }
  .cam-wall-row { display: flex; }
}
/* The wall takes the whole window: the dialog loses its width cap, its
   padding and, on a phone, its bottom-sheet shape and grab handle. */
#modal:has(.cam-wall) { padding: 0; }
.modal-card:has(.cam-wall) {
  width: 100vw; height: 100dvh; max-height: none; border: 0; border-radius: 0;
  padding: 10px 12px calc(12px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; overflow: hidden; background: #05070b;
}
.modal-card:has(.cam-wall)::before { display: none; }
.cam-wall { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 8px; }
.cam-wall:fullscreen { background: #05070b; padding: 10px 12px; }
.wall-head { gap: 6px; flex-wrap: nowrap; min-width: 0; }
.wall-title { margin: 0; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wall-pos { white-space: nowrap; font-variant-numeric: tabular-nums; }
.wall-head .seg { padding: 2px; flex: none; }
.wall-head .seg button { min-height: 32px; padding: 3px 11px; }
.wall-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 36px; flex: none; }
.wall-btn:not(.has-text) { width: 36px; padding: 0; }
/* The cameras, and the events beside them (under them on a portrait
   screen). A clip plays over the cameras, in .wall-main. */
.wall-body { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 10px; }
.wall-main { position: relative; flex: 1; min-width: 0; min-height: 0; display: flex; }
.wall-stage { position: relative; flex: 1; min-width: 0; min-height: 0; overflow: hidden; }
.wall-stage.column { overflow-y: auto; overscroll-behavior: contain; }
.wall-empty { padding: 20px 0; }
/* Each tile's place comes from camwall.js, so a spotlight slides into it. */
.wall-tile {
  position: absolute; margin: 0; padding: 0; min-height: 0; overflow: hidden;
  background: #000; border: 1px solid var(--line); border-radius: var(--r-sm); cursor: zoom-in;
  transition: left .26s var(--ease-out), top .26s var(--ease-out), width .26s var(--ease-out), height .26s var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .wall-tile:hover { background: #000; border-color: var(--accent); }
}
.wall-tile.spot { cursor: zoom-out; border-color: var(--line-2); }
.wall-media { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: contain; }
img.wall-media:not([src]) { visibility: hidden; }
.wall-hd { opacity: 0; transition: opacity .2s ease-out; }
.wall-hd.ready { opacity: 1; }
.wall-tile.down .wall-media { opacity: .45; }
/* Labels at the top, as on the card: the cameras burn their clocks into
   the bottom corners. */
.wall-label, .wall-status {
  position: absolute; top: 6px; font-size: 12px; font-weight: 600; pointer-events: none;
  padding: 2px 8px; border-radius: 999px; background: rgba(0, 0, 0, .62); color: #fff;
}
.wall-label { left: 6px; }
.wall-status { right: 6px; font-weight: 500; color: #c9d1d9; }
.wall-status:empty { display: none; }
.wall-tile.spot .wall-label { font-size: 14px; top: 10px; left: 10px; }
@media (max-width: 600px) {
  .wall-btn.has-text { width: 36px; padding: 0; }
  .wall-btn-text { display: none; }
}
@media (pointer: coarse) {
  .wall-btn { min-height: 44px; }
  .wall-btn:not(.has-text), .wall-btn.has-text { min-width: 44px; }
}
@media (prefers-reduced-motion: reduce) {
  .wall-tile, .wall-hd { transition: none; }
}
/* Recent events: the card's event tiles, in a rail under the cameras. */
.wall-events { flex: none; display: flex; flex-direction: column; gap: 6px; min-width: 0; min-height: 0; }
.cam-wall.no-events .wall-events { display: none; }
.wall-events-head {
  display: flex; align-items: center; gap: 8px; min-height: 20px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
}
.wall-events-title { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wall-events-n { font-variant-numeric: tabular-nums; font-weight: 500; }
.wall-evts {
  display: flex; gap: 8px; overflow-x: auto; overflow-y: hidden; overscroll-behavior: contain; scrollbar-width: none;
}
.wall-evts::-webkit-scrollbar { display: none; }
.wall-evts .evt { flex: 0 0 168px; }
.wall-evt[aria-current="true"] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.wall-events-note { padding: 6px 0; }
/* A landscape screen puts them in a column on the right, newest at the top. */
@media (orientation: landscape) and (min-width: 700px) {
  .wall-body { flex-direction: row; }
  .wall-events { width: clamp(220px, 17vw, 320px); }
  .wall-evts {
    flex-direction: column; overflow-x: hidden; overflow-y: auto; scrollbar-width: thin; padding-right: 2px;
  }
  .wall-evts .evt { flex: none; width: 100%; }
}
/* The header's Events button: a phone always shows the rail. */
.wall-ev-toggle[aria-pressed="true"] { border-color: var(--accent); color: var(--fg); }
@media (max-width: 599px) { .wall-ev-toggle { display: none; } }
/* An event's clip, over the cameras. */
.wall-clip { position: absolute; inset: 0; z-index: 3; display: flex; flex-direction: column; gap: 8px; background: #05070b; }
.wall-clip[hidden] { display: none; }
.wall-clip-head { gap: 8px; min-width: 0; flex-wrap: nowrap; }
.wall-clip-title { min-width: 0; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wall-clip-when { white-space: nowrap; font-variant-numeric: tabular-nums; }
/* The clip keeps its own shape, so its controls sit under the picture:
   centered on a landscape screen, at the top on a portrait one. */
.wall-clip-stage { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; }
.wall-clip-media {
  display: block; width: 100%; height: auto; max-height: 100%; object-fit: contain;
  background: #000; border-radius: var(--r-sm);
}
@media (orientation: portrait) { .wall-clip-stage { align-items: flex-start; } }

/* TV favorites */
.tvfavs { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
/* Two to a row on a phone: an odd one out takes the row instead of leaving
   half of it empty. */
@media (max-width: 459px) {
  .tvfav:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
.tvfav { padding: 8px; gap: 5px; }
.tvfav .tile-logo { height: 34px; }
.tvfav .tile-logo img { max-height: 34px; }

/* camera events */
/* Recent events: a rail under the camera grid, each event a picture like
   the cameras above it, with the camera on a pill and what and when on a
   shade at the bottom. Hover plays the highlight GIF. */
.cam-events { margin-top: 14px; }
.cam-events .music-head { margin: 0 0 8px; }
.evts {
  display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x proximity;
  scrollbar-width: none; overscroll-behavior-x: contain;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent);
}
.evts::-webkit-scrollbar { display: none; }
.evts.at-end { -webkit-mask-image: none; mask-image: none; }
.evt {
  position: relative; flex: 0 0 196px; aspect-ratio: 16 / 9; padding: 0; min-height: 0;
  overflow: hidden; text-align: left; background: var(--card-2);
  border: 1px solid var(--line); border-radius: var(--r-sm); scroll-snap-align: start;
}
@media (hover: hover) and (pointer: fine) {
  .evt:hover { background: var(--card-2); border-color: var(--accent); }
}
.evt img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
/* The highlight GIF is cropped to the action; show all of it. */
.evt img.playing { object-fit: contain; }
.evt::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 58%;
  background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, .82)); pointer-events: none;
}
.evt-cam, .evt-now {
  position: absolute; top: 6px; z-index: 1; font-size: 11.5px; font-weight: 600;
  padding: 2px 8px; border-radius: 999px; background: rgba(0, 0, 0, .62); color: #fff;
}
.evt-cam { left: 6px; max-width: calc(100% - 64px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.evt-now { right: 6px; background: var(--bad); color: #1a0608; }
.evt-info {
  position: absolute; left: 8px; right: 8px; bottom: 6px; z-index: 1;
  display: flex; align-items: baseline; gap: 8px; color: #fff; font-size: 12.5px; font-weight: 600;
}
.evt-info .truncate { flex: 1 1 auto; min-width: 0; }
.evt-time { flex: 0 0 auto; font-weight: 500; color: rgba(255, 255, 255, .78); font-variant-numeric: tabular-nums; }
/* A phone shows two events and the edge of a third, so the rail reads as one. */
@media (max-width: 600px) { .evt { flex-basis: max(136px, calc((100% - 16px) / 2.25)); } }

/* New movies: one row of posters that scrolls sideways, snapping to a
   poster. The edges fade while there is more; arrows page it on a desktop. */
.movies-wrap { position: relative; margin: 0 -14px; }
.movies {
  display: flex; gap: 14px; overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-padding-left: 14px; scrollbar-width: none;
  padding: 4px 14px 6px;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 40px), transparent);
  mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 40px), transparent);
}
.movies::-webkit-scrollbar { display: none; }
.movies-wrap.at-start .movies {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent);
}
.movies-wrap.at-end .movies { -webkit-mask-image: none; mask-image: none; }
.movie { margin: 0; min-width: 0; flex: 0 0 148px; scroll-snap-align: start; }
.movie-art {
  position: relative; aspect-ratio: 2 / 3; border-radius: 10px; overflow: hidden;
  background: var(--bg-2); box-shadow: 0 6px 18px rgba(0, 0, 0, .35), 0 0 0 1px rgba(255, 255, 255, .06);
  transition: transform 220ms var(--ease-out), box-shadow 220ms var(--ease-out);
}
.movie-art img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1;
  transition: transform 400ms var(--ease-out);
}
/* A scrim at the foot of the poster keeps the time chip readable on any art. */
.movie-shade {
  position: absolute; inset: auto 0 0 0; height: 38%; z-index: 2; pointer-events: none;
  background: linear-gradient(to top, rgba(0, 0, 0, .7), transparent);
}
@media (hover: hover) {
  .movie:hover .movie-art { transform: translateY(-4px); box-shadow: 0 12px 26px rgba(0, 0, 0, .5), 0 0 0 1px rgba(255, 255, 255, .1); }
  .movie:hover .movie-art img { transform: scale(1.04); }
}
@media (prefers-reduced-motion: reduce) {
  .movie-art, .movie-art img { transition: none; }
}
.rail-arrow {
  position: absolute; top: calc(50% - 30px); z-index: 3; width: 40px; height: 40px; min-height: 0; padding: 0;
  display: grid; place-items: center; border-radius: 50%;
  background: rgba(11, 14, 20, .82); border: 1px solid var(--line-2); color: var(--fg);
  opacity: 0; transition: opacity 160ms ease-out; backdrop-filter: blur(6px);
}
.rail-arrow.prev { left: 6px; }
.rail-arrow.next { right: 6px; }
@media (hover: hover) {
  .movies-wrap:hover .rail-arrow, .rail-wrap:hover .rail-arrow { opacity: 1; }
  .movies-wrap.at-start .rail-arrow.prev, .movies-wrap.at-end .rail-arrow.next,
  .rail-wrap.at-start .rail-arrow.prev, .rail-wrap.at-end .rail-arrow.next { opacity: 0; pointer-events: none; }
}
@media (hover: none) { .rail-arrow { display: none; } }
.movie-noart {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 34px; font-weight: 700; color: var(--muted-2);
}
.movie-when {
  position: absolute; z-index: 3; left: 7px; bottom: 7px;
  padding: 2px 7px; border-radius: 999px; font-size: 11.5px; font-weight: 650;
  background: rgba(8, 11, 16, .78); color: #e8edf5; backdrop-filter: blur(4px);
}
/* Dark text: white on the accent blue measured under 4.5:1. */
.movie-when.fresh { background: var(--accent); color: #07111f; }
.movie-title {
  margin-top: 8px; font-size: 13px; font-weight: 600; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
@media (max-width: 600px) {
  .movie { flex-basis: 34%; }
}

/* Movie tiles are buttons now (they open the sheet). */
button.movie { all: unset; display: block; box-sizing: border-box; cursor: pointer; flex: 0 0 148px; scroll-snap-align: start; min-width: 0; }
button.movie:focus-visible .movie-art { outline: 2px solid var(--accent); outline-offset: 2px; }
.movie-q {
  display: inline-block; margin-top: 4px; padding: 1px 6px; border-radius: 5px;
  font-size: 11px; font-weight: 650; color: var(--muted); background: var(--bg-2); border: 1px solid var(--line);
}
@media (max-width: 600px) { button.movie { flex-basis: 34%; } }

/* A movie's sheet: the poster big beside the facts. */
.modal-card:has(.msheet) { width: min(760px, 100%); }
.msheet-grid { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 20px; }
.msheet-art { aspect-ratio: 2 / 3; border-radius: 12px; overflow: hidden; background: var(--bg-2); position: relative;
  box-shadow: 0 14px 36px rgba(0, 0, 0, .55), 0 0 0 1px rgba(255, 255, 255, .08); animation: msheet-in 280ms var(--ease-out); }
.msheet-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
@keyframes msheet-in { from { opacity: 0; transform: scale(.94); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .msheet-art { animation: none; } }
.msheet-title { margin: 0 0 8px; font-size: 24px; line-height: 1.2; }
.msheet-facts { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.msheet-fact { display: inline-flex; align-items: center; gap: 3px; padding: 3px 9px; border-radius: 999px;
  background: var(--card-2); border: 1px solid var(--line); font-size: 13px; font-weight: 600; }
.msheet-fact .icon { color: var(--warn); }
.msheet-fact.cert { border-color: var(--line-2); }
.msheet-fact.fresh { color: var(--bad); }
.msheet-plot { font-size: 14.5px; line-height: 1.55; color: var(--fg); margin: 10px 0 12px; }
.chip.q { font-weight: 650; }
.msheet-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 14px; }
.msheet-actions button, .msheet-actions .btn { display: inline-flex; align-items: center; gap: 6px; }
.msheet-actions .btn { text-decoration: none; }
/* On a phone the poster sits small beside the title instead of above it:
   full width it pushed Radarr's buttons to the bottom edge of the screen. */
@media (max-width: 560px) {
  /* The poster spans the first three rows; the third takes the spare
     height, so the year and genres stay under the title. */
  /* Four rows, the last taking the spare height: with three, a show's
     genres (its fourth line, after the episode and the facts) dropped below
     the poster's foot. */
  .msheet-grid { grid-template-columns: 104px minmax(0, 1fr); grid-template-rows: auto auto auto minmax(0, 1fr); gap: 8px 14px; align-items: start; }
  .msheet-art { grid-row: 1 / span 4; max-width: none; margin: 0; border-radius: 9px; }
  /* Close sits in the corner beside the title rather than on a row of its
     own over the poster. */
  .msheet { position: relative; }
  .msheet > .row:first-child { position: absolute; top: 0; right: 0; margin: 0 !important; z-index: 1; }
  .msheet-title { padding-right: 84px; }
  .msheet-info { display: contents; }
  .msheet-info > * { grid-column: 2; }
  .msheet-info > .msheet-plot, .msheet-info > .msheet-q, .msheet-info > .msheet-radarr { grid-column: 1 / -1; }
  .msheet-title { font-size: 19px; }
}
.msheet-art.settled { animation: none; }
/* Radarr's side of a movie: status, the file, a better posted quality, then the options. */
.msheet-radarr { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.msheet-status { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.msheet-radarr .msheet-actions { margin-top: 10px; gap: 8px; }
.msheet-opt { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); }
.msheet-opt select { min-height: 40px; max-width: 220px; }
.msheet-actions button.ghost.on { border-color: var(--ok); color: var(--ok); }

/* Coming this week: small posters with the day and date on each. */
/* Tiles keep their own height: a button stretched to the row centers its
   poster, and one-line titles sat lower than two-line ones. */
.coming-rail { display: flex; align-items: flex-start; gap: 12px; overflow-x: auto; scroll-snap-type: x proximity; scrollbar-width: none; padding: 2px 0 6px; }
.coming-rail::-webkit-scrollbar { display: none; }
.coming-tile { flex: 0 0 96px; min-width: 0; scroll-snap-align: start; }
/* A poster Radarr or Sonarr has is a button that opens its sheet. */
button.coming-tile {
  display: block; padding: 0; border: 0; background: transparent; color: inherit;
  text-align: left; font: inherit; cursor: pointer; min-height: 0;
}
@media (hover: hover) and (pointer: fine) {
  button.coming-tile:hover { background: transparent; }
}
@media (hover: hover) {
  button.coming-tile:hover .coming-art { transform: translateY(-3px); box-shadow: 0 10px 22px rgba(0, 0, 0, .45); }
}
.coming-art { transition: transform .2s var(--ease-out), box-shadow .2s var(--ease-out); }
.msheet-sub { font-size: 14px; color: var(--muted); margin: -4px 0 8px; }
.coming-art { position: relative; aspect-ratio: 2 / 3; border-radius: 8px; overflow: hidden; background: var(--bg-2);
  box-shadow: 0 4px 12px rgba(0, 0, 0, .35); }
.coming-art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1; }
.coming-day { position: absolute; z-index: 2; left: 5px; top: 5px; padding: 2px 7px; border-radius: 999px;
  font-size: 11px; font-weight: 750; background: rgba(8, 11, 16, .82); color: #e8edf5; }
.coming-title { margin-top: 6px; font-size: 12.5px; font-weight: 650; line-height: 1.2;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.coming-sub { font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* On a phone a poster alone in its rail (New today, often) is a row: the
   poster, and beside it the name and episode, instead of one 96px tile and
   a row of nothing. Wider, the tile stays: the shorter card unbalanced the
   wide Desk's columns. */
@media (max-width: 719px) {
  .coming-rail:has(> .coming-tile:only-child) > .coming-tile {
    flex: 1 1 auto; display: grid; grid-template-columns: 72px minmax(0, 1fr); column-gap: 12px; text-align: left;
  }
  .coming-rail:has(> .coming-tile:only-child) .coming-art { grid-row: 1 / span 2; }
  .coming-rail:has(> .coming-tile:only-child) .coming-title { align-self: end; font-size: 15px; }
  .coming-rail:has(> .coming-tile:only-child) .coming-sub { align-self: start; font-size: 13px; }
}

/* The weather card, laid out like Google's. */
.wx-svg { display: inline-block; flex: 0 0 auto; }
.wx-svg svg { display: block; }
.fc-now { display: grid; grid-template-columns: auto auto 1fr; grid-template-areas: "icon temp facts" "what what what";
  align-items: center; column-gap: 8px; row-gap: 2px; }
.fc-now > .wx-svg { grid-area: icon; margin: -8px -4px; }
.fc-temp { grid-area: temp; font-size: 40px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; }
.fc-facts { grid-area: facts; justify-self: end; font-size: 13px; color: var(--muted); display: grid; gap: 3px; }
.fc-facts > div { display: inline-flex; align-items: center; gap: 2px; }
.fc-what { grid-area: what; display: flex; gap: 8px; align-items: baseline; font-size: 13.5px; margin-top: 6px; }
.fc-modes { margin: 12px 0 2px; }
.fc-modes button { min-height: 30px; padding: 3px 12px; font-size: 13px; }
.fc-graph { overflow-x: auto; scrollbar-width: none; margin: 6px -4px 2px; padding: 0 4px; }
.fc-graph::-webkit-scrollbar { display: none; }
.fc-graph svg { display: block; }
.fc-line { fill: none; stroke: #fbbf24; stroke-width: 2.5; stroke-linejoin: round; }
.fc-area { fill: rgba(251, 191, 36, .16); }
.fc-bar { fill: rgba(90, 169, 255, .75); }
.fc-val { fill: var(--fg); font-size: 12px; font-weight: 650; text-anchor: middle; }
.fc-time { fill: var(--muted); font-size: 11px; text-anchor: middle; }
.fc-days { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; margin-top: 10px; }
.fc-day { display: grid; justify-items: center; gap: 2px; padding: 8px 2px; border-radius: 10px; text-align: center; min-width: 0; }
.fc-day.today { background: var(--card-2); }
.fc-dname { font-size: 12.5px; font-weight: 650; color: var(--muted); }
.fc-day.today .fc-dname { color: var(--fg); }
/* In a narrow card the high sits over the low: side by side, a 45px day ran
   "56° 38°" into the next one on a phone and in the tablet's side column. A
   wide card keeps them on one line, and the Desk's columns their balance. */
.fc-days { container-type: inline-size; }
.fc-hl { display: grid; justify-items: center; line-height: 1.25; font-size: 13.5px; font-variant-numeric: tabular-nums; }
@container (min-width: 440px) {
  .fc-hl { display: flex; gap: 5px; line-height: inherit; }
  .fc-hl .fc-nohi { display: none; }
}
.fc-pop { display: inline-flex; align-items: center; font-size: 11px; color: var(--accent); min-height: 14px; }
@media (max-width: 340px) { .fc-days { grid-template-columns: repeat(7, 48px); overflow-x: auto; } }

/* The Report tab's styles are in report.css. */

/* phone alerts */
.alerts { display: flex; flex-direction: column; max-height: 340px; overflow-y: auto; }
.alerts:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
.alert-row {
  display: flex; gap: 10px; align-items: center; width: 100%; min-width: 0;
  padding: 7px 4px; border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
  background: none; color: inherit; text-align: left; font: inherit; cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
  .alert-row:hover { background: var(--card-2); }
}
.alert-row:active { background: var(--line); }
.alert-art {
  flex: 0 0 auto; width: 64px; height: 40px; border-radius: 6px;
  object-fit: cover; background: var(--bg-2);
}
.alert-icon { display: grid; place-items: center; color: var(--muted); }
.alert-text { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.alert-row:last-child { border-bottom: 0; }
.alert-row.fresh { animation: desk-fresh 2.4s ease-out; }
@keyframes desk-fresh { from { background: #101f30; } to { background: transparent; } }
@media (prefers-reduced-motion: reduce) { .alert-row.fresh { animation: none; } }
.alert-head { display: flex; gap: 8px; align-items: baseline; }
.alert-title { font-weight: 600; font-size: 13.5px; flex: 1; min-width: 0; }
.alert-msg { display: block; }
.alerts-more { display: none; }
@media (max-width: 719px) {
  .alerts { max-height: none; overflow: visible; }
  .alerts:not(.all) .alert-row:nth-child(n+7) { display: none; }
  .alerts:not(.all) .alert-row:nth-child(6) { border-bottom: 0; }
  .alerts-more { display: block; width: 100%; min-height: 44px; margin-top: 6px; }
  /* Two lines: the camera ("...on the Doorbell") came last and was cut. */
  .alert-title { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  .alert-head { align-items: flex-start; }
}
.nowrap { white-space: nowrap; }

/* links */
.dlinks { display: grid; gap: 6px; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.dlink {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 7px 10px; border-radius: var(--r-sm); background: var(--bg-2);
  border: 1px solid var(--line); color: var(--fg); font-size: 13.5px;
}
@media (hover: hover) and (pointer: fine) {
  .dlink:hover { border-color: var(--line-2); text-decoration: none; }
}
.ldot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted-2); flex: 0 0 auto; }
/* Finger-sized targets for the Desk's small controls on touch screens. */
@media (pointer: coarse) {
  .dlink { min-height: 44px; }
  .desk-sec .section-head a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 4px; }
  /* A 44px target for the "Now playing" pill without making it taller:
     an invisible edge around it takes the tap. */
  .desk-watching::before { content: ""; position: absolute; inset: -8px -2px; }
  .wx-alert { min-height: 44px; }
}
.ldot.ok { background: var(--ok); }
.ldot.bad { background: var(--bad); }

/* music card: the station playing on top, favorites to switch to below */
/* Tinted with the playing song's album color (palette.js). */
.music-now {
  display: flex; align-items: center; gap: 10px; padding: 8px; margin-bottom: 10px;
  border: 1px solid color-mix(in srgb, var(--np-accent) 30%, transparent); background: var(--np-bg); border-radius: var(--r-sm);
}
.music-now.paused { border-color: var(--line); background: var(--bg-2); }
.music-now img, .music-row img, .music-noart {
  width: 40px; height: 40px; border-radius: 8px; object-fit: cover; flex: 0 0 auto; background: var(--bg-2);
}
.music-meta { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; text-align: left; }
.music-name { font-size: 13.5px; font-weight: 620; }
.music-text { font-size: 12px; color: var(--np-accent); }
.music-now.paused .music-text, .music-row .music-text { color: var(--muted); }
.music-list { display: flex; flex-direction: column; gap: 2px; }
.music-head { margin: 0 2px 4px; text-transform: uppercase; letter-spacing: .04em; }
.music-row {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 52px; padding: 6px;
  background: transparent; border-color: transparent; text-align: left;
}
@media (hover: hover) and (pointer: fine) {
  .music-row:hover { background: var(--bg-2); border-color: var(--line); }
}
@media (pointer: coarse) {
  .music-now .dock-btn { width: 44px; height: 44px; min-height: 44px; }
}

/* activity: who is watching now, then the last few sessions */
.act-list { display: flex; flex-direction: column; }
.act-row {
  display: flex; align-items: center; gap: 10px; min-width: 0;
  padding: 7px 2px; border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.act-row:last-child { border-bottom: 0; }
.act-meta { flex: 1 1 auto; min-width: 0; }
.act-who { font-weight: 600; }
.act-tag {
  flex: 0 0 auto; width: 48px; text-align: center; font-size: 11px; font-weight: 650; padding: 1px 0;
  border-radius: 999px; background: var(--card-2); color: var(--accent);
}
.act-tag.radio { color: #a78bfa; }
.act-tag.plex { color: var(--plex); }

/* ---------- homelab ---------- */

.lab { display: flex; flex-direction: column; gap: 16px; }
/* The section cards sat edge to edge and read as one double border. */
#lab-sections { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.lab-head { display: flex; gap: 10px; }
.lab-filter { flex: 1 1 auto; max-width: 360px; min-width: 0; }
.lab-sec { padding: 14px; min-width: 0; }
.lab-sec .section-head { margin-bottom: 10px; }
.lab-grid { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.lab-tile {
  display: flex; align-items: center; gap: 10px; min-width: 0; min-height: 52px;
  padding: 8px 10px; border-radius: var(--r-sm); border: 1px solid var(--line);
  background: var(--bg-2); color: var(--fg); text-decoration: none;
}
@media (hover: hover) and (pointer: fine) {
  .lab-tile:hover { border-color: var(--accent); }
}
/* A phone fits two tiles a row (the rule near the end of the Homelab
   block): one each left most of every row empty. */
@media (max-width: 560px) {
  .lab-tile { gap: 8px; padding: 8px; }
  /* Two lines rather than "actualbud...", and no green dot on each running
     tile: the header says how many run, and a stopped one keeps its grey dot. */
  .lab-name {
    overflow: hidden; overflow-wrap: anywhere; line-height: 1.25; white-space: normal;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  }
  .lab-tile:not(.off) .lab-dot { display: none; }
}
/* Stopped: a grey dot, a muted name and a faded icon. Not opacity on the
   whole tile: that took its text below contrast. */
.lab-tile.off .lab-name { color: var(--muted); }
.lab-tile.off .lab-icon { opacity: .5; }
.lab-icon { width: 28px; height: 28px; flex: 0 0 auto; object-fit: contain; border-radius: 6px; }
.lab-letter {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--card-2); color: var(--muted); font-weight: 650; font-size: 14px;
}
.lab-meta { flex: 1 1 auto; min-width: 0; }
.lab-name { font-weight: 600; font-size: 14px; }
.lab-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); flex: 0 0 auto; }
.off .lab-dot { background: var(--muted-2); }
.lab-other { margin-top: 12px; }
/* Stopped containers, closed at the end: the grid above is what runs. */
.lab-stopped { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 4px; }
.lab-stopped > summary {
  display: flex; align-items: center; gap: 6px; min-height: 40px; cursor: pointer; list-style: none;
  font-size: 13px; font-weight: 650; color: var(--muted);
}
.lab-stopped > summary::-webkit-details-marker { display: none; }
.lab-stopped > summary .icon { transition: transform .2s var(--ease-out); transform: rotate(-90deg); }
.lab-stopped[open] > summary .icon { transform: none; }
.lab-stopped .lab-grid { margin-top: 6px; }
.lab-chip { display: inline-flex; align-items: center; gap: 6px; }
.lab-chip.off { color: var(--muted); }
@media (max-width: 560px) {
  .lab-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Two to a row: an odd one out takes the row. */
  .lab-grid > .lab-tile:last-child:nth-child(odd) { grid-column: 1 / -1; }
  /* Full width, so its edge lines up with the cards below it. */
  .lab-filter { max-width: none; }
}
@media (max-width: 339px) { .lab-grid { grid-template-columns: minmax(0, 1fr); } }

/* locked */
.desk-lock { max-width: 460px; margin: 40px auto; }
.desk-lock input { flex: 1; min-width: 0; }
.desk-lock-err { color: var(--bad); min-height: 20px; margin-top: 8px; }

/* camera pop-up: above everything except the toast */
.cam-pop {
  position: fixed; z-index: 95; right: 18px; top: calc(var(--header-h) + 12px);
  width: min(520px, calc(100vw - 24px));
  background: var(--card); border: 1px solid var(--accent); border-radius: var(--r-lg);
  box-shadow: var(--shadow); padding: 12px;
}
.cam-pop-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.cam-pop-head strong { min-width: 0; }
.cam-pop-video video, .cam-pop-video img {
  display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: contain; background: #000; border-radius: var(--r-sm);
}
@media (max-width: 560px) {
  .cam-pop { right: 12px; left: 12px; width: auto; top: calc(var(--header-h) + 8px); }
}

.icon { display: inline-flex; align-items: center; justify-content: center; line-height: 0; }
.icon svg { display: block; }

/* ---------- mcdPortal ---------- */

/* A rail of sub-views beside the content on a desktop. On a phone the rail
   becomes wrapping chips above it: a scrolling row would hide the tail, the
   way Activity's "Logs" once hid. minmax(0, 1fr) and min-width: 0 keep a
   wide table scrolling inside its card instead of widening the page. */
.mcdp { display: grid; gap: 20px; grid-template-columns: 188px minmax(0, 1fr); align-items: start; }
.mcdp-rail {
  position: sticky; top: calc(var(--header-h) + 18px);
  display: flex; flex-direction: column; gap: 1px; min-width: 0;
}
.mcdp-rail-group {
  padding: 12px 11px 4px; font-size: 11px; font-weight: 650;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}
.mcdp-rail-group:first-child { padding-top: 0; }
.mcdp-rail button {
  display: flex; align-items: center; gap: 9px; width: 100%;
  background: transparent; border: 0; text-align: left;
  padding: 7px 11px; min-height: 36px; border-radius: var(--r-sm);
  color: var(--muted); font-size: 14px;
}
.mcdp-rail button .icon { opacity: .85; }
@media (hover: hover) and (pointer: fine) {
  .mcdp-rail button:hover { background: var(--card); color: var(--fg); }
}
.mcdp-rail button[aria-current="true"] { background: var(--card-2); color: var(--fg); }
.mcdp-rail button[aria-current="true"] .icon { color: var(--accent); opacity: 1; }
.mcdp-main { display: grid; gap: 16px; min-width: 0; }
.mcdp-main > * { min-width: 0; }
.mcdp-main > .desk-banner { margin-bottom: 0; }
@media (max-width: 900px) {
  .mcdp { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .mcdp-rail { position: static; flex-direction: row; flex-wrap: wrap; gap: 6px; }
  .mcdp-rail-group { display: none; }
  .mcdp-rail button {
    width: auto; gap: 5px; padding: 4px 11px 4px 8px; min-height: 34px;
    border: 1px solid var(--line); border-radius: 999px; background: var(--bg-2); font-size: 13px;
  }
  .mcdp-rail button .icon svg { width: 16px; height: 16px; }
  .mcdp-rail button[aria-current="true"] {
    border-color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, var(--bg-2));
  }
}
/* On a phone, thirteen chips took four rows at 411px and five at 344px
   before any content. Five columns of icon-over-label, like the tab bar,
   take three. */
@media (max-width: 480px) {
  .mcdp-rail {
    display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 4px; padding: 5px;
    background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  }
  .mcdp-rail button {
    flex-direction: column; justify-content: center; gap: 3px; min-width: 0; padding: 6px 2px;
    border: 1px solid transparent; border-radius: var(--r-sm); background: transparent;
    font-size: 10.5px; font-weight: 600; line-height: 1.2;
  }
  .mcdp-rail button > span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mcdp-rail button .icon svg { width: 20px; height: 20px; }
}
/* The Fold's 344px cover screen: "Dashboard" fits at 10px, not 10.5px. */
@media (max-width: 360px) {
  .mcdp-rail button { font-size: 10px; }
}

.mcdp-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px; min-width: 0; }
.mcdp-head h3 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 17px; min-width: 0; }
.mcdp-head h3 .icon { color: var(--muted); }
.mcdp-card .mcdp-head h3, .mcdp-life-card .mcdp-head h3, .mcdp-logs .mcdp-head h3 { font-size: 15px; }
.mcdp-count { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.mcdp-actions { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 6px; min-width: 0; }
.mcdp-card { padding: 14px; display: grid; gap: 12px; min-width: 0; }
.mcdp-card > * { min-width: 0; }
.mcdp-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; }
.mcdp-ibtn {
  display: inline-grid; place-items: center; flex: 0 0 auto;
  width: 36px; height: 36px; min-height: 36px; padding: 0;
  background: transparent; border-color: transparent; color: var(--muted);
}
@media (hover: hover) and (pointer: fine) {
  .mcdp-ibtn:hover { background: var(--card-2); color: var(--fg); }
}
.mcdp-ibtn.danger { border-color: transparent; color: var(--muted); }
@media (hover: hover) and (pointer: fine) {
  .mcdp-ibtn.danger:hover { background: #2a1418; color: var(--bad); }
}
.mcdp-filter { width: 200px; max-width: 100%; flex: 0 1 200px; min-width: 0; }
/* 44px is the smallest target Apple and Google both call reliably
   tappable. These rules match their own selectors' specificity, since a
   media query adds none. */
@media (pointer: coarse) {
  .mcdp-rail button { min-height: 44px; }
  .mcdp-ibtn { width: 44px; height: 44px; min-height: 44px; }
  button.mcdp-btn { min-height: 44px; }
  .mcdp-check input { width: 24px; height: 24px; }
}

/* Numbers first: an icon in its tone, the figure, a short label. */
.mcdp-stats { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); }
.mcdp-stat {
  display: flex; align-items: center; gap: 11px; min-width: 0; padding: 12px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
}
.mcdp-card .mcdp-stat { background: var(--bg-2); }
.mcdp-stat.t-warn, .mcdp-stat.t-bad { border-color: color-mix(in srgb, var(--tone) 40%, var(--line)); }
.mcdp-stat-text { min-width: 0; }
.mcdp-stat-v {
  font-size: 22px; font-weight: 680; line-height: 1.15; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mcdp-stat-k { font-size: 12px; color: var(--muted); line-height: 1.3; }
.mcdp-stat-sub { font-size: 12px; font-weight: 620; color: var(--tone); }
.mcdp-stats-sm { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
@media (max-width: 480px) {
  .mcdp-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mcdp-stat { padding: 10px; gap: 9px; }
  .mcdp-stat-v { font-size: 19px; }
}

/* A record: icon, name with its state, details, a figure, its buttons.
   On a phone the figure drops under the details and the buttons get their
   own row, right-aligned. */
.mcdp-list { display: grid; gap: 8px; }
.mcdp-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; align-items: center; gap: 8px 12px;
  padding: 10px 12px; min-width: 0;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
}
.mcdp-card .mcdp-row { background: var(--bg-2); }
.mcdp-row-body { min-width: 0; }
.mcdp-row-title { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; font-weight: 620; min-width: 0; }
.mcdp-row-name { min-width: 0; overflow-wrap: anywhere; }
.mcdp-row-sub {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; margin-top: 2px;
  font-size: 13px; color: var(--muted); min-width: 0;
}
.mcdp-row-sub > * { min-width: 0; overflow-wrap: anywhere; }
.mcdp-sep { color: var(--muted-2); }
.mcdp-row-side { justify-self: end; text-align: right; min-width: 0; }
.mcdp-row-actions { display: flex; flex-wrap: wrap; gap: 2px; justify-self: end; }
@media (max-width: 640px) {
  .mcdp-row { display: flex; flex-wrap: wrap; gap: 4px 10px; }
  .mcdp-row > .badge, .mcdp-row > .avatar { align-self: flex-start; }
  .mcdp-row-body { flex: 1 1 calc(100% - 52px); }
  .mcdp-row-side { flex: 0 1 auto; margin-left: 42px; text-align: left; }
  .mcdp-row-actions { flex: 0 0 auto; margin-left: auto; }
}
.mcdp-metrics { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }
@media (max-width: 640px) { .mcdp-metrics { justify-content: flex-start; } }
.mcdp-amount { font-size: 17px; font-weight: 680; font-variant-numeric: tabular-nums; white-space: nowrap; }
.mcdp-amount-sub { font-size: 12px; font-weight: 500; color: var(--muted); }

.mcdp-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--tone); flex: 0 0 auto; }
.mcdp-state {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px;
  font-size: 13px; font-weight: 620; color: var(--tone); background: color-mix(in srgb, var(--tone) 12%, transparent);
}

/* Site health: one tile a site, its dot and word in the PM2 state's tone. */
.mcdp-health { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.mcdp-tile {
  display: flex; align-items: center; gap: 10px; min-width: 0; padding: 9px 11px;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-sm);
}
.mcdp-tile.t-bad { border-color: color-mix(in srgb, var(--bad) 45%, var(--line)); }
.mcdp-tile-text { flex: 1 1 auto; min-width: 0; }
.mcdp-tile-name { font-weight: 600; font-size: 14px; }
.mcdp-tile-state { font-size: 12px; font-weight: 650; color: var(--tone); white-space: nowrap; }
@media (max-width: 480px) { .mcdp-health { grid-template-columns: minmax(0, 1fr); } }

.mcdp-feed { display: grid; }
.mcdp-feed-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 10px; align-items: baseline;
  padding: 8px 2px; border-bottom: 1px solid var(--line);
}
.mcdp-feed-row:last-child { border-bottom: 0; }
.mcdp-feed-body { min-width: 0; }

.mcdp-table td, .mcdp-table th { white-space: nowrap; }

/* Forms in the dialog. */
.mcdp-form { display: grid; gap: 2px; min-width: 0; }
.mcdp-form-title { display: flex; align-items: center; gap: 8px; margin: 0 0 14px; }
.mcdp-form-title .icon { color: var(--accent); }
.mcdp-form-err { color: var(--bad); font-size: 13.5px; }
.mcdp-form-err:not(:empty) { margin: 4px 0 8px; }
.mcdp-form-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 8px; }
.mcdp-hint { display: block; margin-top: 4px; font-size: 12px; color: var(--muted); }
.mcdp-check { display: flex; align-items: center; gap: 9px; margin: 2px 0 12px; cursor: pointer; }
.mcdp-check input { width: 20px; height: 20px; min-height: 0; padding: 0; flex: 0 0 auto; accent-color: var(--accent); }
.mcdp-form textarea, .mcdp-note-add textarea { resize: vertical; min-height: 76px; }

.mcdp-log {
  margin: 12px 0 0; max-height: 60vh; overflow: auto; padding: 12px;
  background: #05070b; border: 1px solid var(--line); border-radius: var(--r-sm); color: #c9d4e3;
  font: 12.5px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  white-space: pre-wrap; overflow-wrap: anywhere;
}

.mcdp-notes { display: grid; gap: 8px; }
.mcdp-note { padding: 11px 12px; background: var(--card); border: 1px solid var(--line); border-radius: var(--r); min-width: 0; }
.mcdp-notes-modal .mcdp-note { background: var(--bg-2); }
.mcdp-note-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.mcdp-note-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.mcdp-note-add { display: grid; gap: 8px; margin-bottom: 12px; min-width: 0; }
.mcdp-main > .mcdp-note-add { margin-bottom: 0; padding: 12px; }
.mcdp-note-add > button { justify-self: end; }
.mcdp-note-add-row { display: flex; flex-wrap: wrap; gap: 8px; }
.mcdp-note-add-row select { flex: 1 1 200px; width: auto; min-width: 0; }

.mcdp-provision { gap: 4px; padding: 16px; }
.mcdp-provision .desk-banner { margin: 4px 0 8px; }
.mcdp-steps { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 6px; }
.mcdp-steps li { display: flex; align-items: flex-start; gap: 8px; min-width: 0; overflow-wrap: anywhere; }
.mcdp-steps li .icon { color: var(--tone); margin-top: 2px; }
.mcdp-secret {
  margin-top: 10px; padding: 12px; display: grid; gap: 8px;
  border: 1px solid #5c4a1d; background: #241f10; border-radius: var(--r-sm);
}
.mcdp-secret-row { display: flex; gap: 8px; }
.mcdp-secret-row input { flex: 1 1 auto; min-width: 0; }

.mcdp-life { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); align-items: start; }
.mcdp-life-card { padding: 14px; display: grid; gap: 8px; min-width: 0; }
.mcdp-life-items { display: grid; }
.mcdp-life-item {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0 6px; align-items: center;
  padding: 7px 0; border-top: 1px solid var(--line);
}
.mcdp-life-label { grid-column: 1; font-size: 12px; font-weight: 600; color: var(--muted); }
.mcdp-life-value { grid-column: 1; margin-top: 1px; white-space: pre-wrap; overflow-wrap: anywhere; min-width: 0; }
.mcdp-life-value .mcdp-sep { margin: 0 6px; }
.mcdp-life-acts { grid-column: 2; grid-row: 1 / span 2; display: flex; }
@media (max-width: 480px) { .mcdp-life { grid-template-columns: minmax(0, 1fr); } }

.mcdp-sched { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
.mcdp-sched-card {
  display: flex; align-items: center; gap: 11px; min-width: 0; padding: 11px 12px;
  background: var(--bg-2); border: 1px solid color-mix(in srgb, var(--tone) 35%, var(--line)); border-radius: var(--r);
}
.mcdp-sched-text { flex: 1 1 auto; min-width: 0; }
.mcdp-sched-name { font-size: 16px; font-weight: 680; }
.mcdp-sched-state { font-weight: 650; color: var(--tone); }
@media (max-width: 480px) { .mcdp-sched { grid-template-columns: minmax(0, 1fr); } }

.mcdp-rings { display: flex; gap: 12px; }
.mcdp-ring { display: grid; justify-items: center; gap: 2px; font-size: 11px; color: var(--muted); }

.mcdp-locked { display: grid; justify-items: center; gap: 8px; max-width: 460px; margin: 30px auto; padding: 28px 20px; text-align: center; }
.mcdp-locked h3 { margin: 4px 0 0; }
.mcdp-locked p { margin: 0 0 8px; }
.mcdp-problem { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.mcdp-problem > div { flex: 1 1 200px; min-width: 0; }

/* Compact controls that set their own height still get a 44px target on a
   touch screen: the weather tabs were 30px, SD/HD 28px, the status chip
   28px, and a site link 19px. The chip and links keep their look and reach
   past it. Last in the file: those controls' own rules come later than
   the general touch block and would win. */
@media (pointer: coarse) {
  .fc-modes button, .live-modal .seg button, .radio-switch button { min-height: 44px; }
  .hchip, .mcdp-link { position: relative; }
  .hchip::after { content: ""; position: absolute; inset: -8px -3px; }
  .mcdp-link { display: inline-block; padding: 12px 0; margin: -12px 0; }
}

/* ---------- McDPortal as an app ---------- */

/* The offer to install it (Settings, and under the Scores tab's games). */
.app-offer { display: flex; align-items: center; gap: 12px; }
.app-offer-text { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; line-height: 1.35; }
.app-offer .head-chip { flex: none; text-decoration: none; }
/* The chip is the card's one action, so it gets a finger-sized target. */
@media (pointer: coarse) {
  .app-offer .head-chip { min-height: 44px; padding: 0 16px 0 14px; justify-content: center; }
}
.app-help-steps { margin: 8px 0 12px; padding-left: 22px; line-height: 1.7; }

/* On a network that can't reach /admin, the app says so at the bottom. */
.away-bar {
  position: fixed; z-index: 90; left: 12px; right: 12px; bottom: calc(12px + env(safe-area-inset-bottom));
  display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--card-2); color: var(--fg); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  font-size: 14px; line-height: 1.35;
}
.away-bar > span { flex: 1 1 auto; min-width: 0; }
.away-bar .icon { flex: none; display: inline-flex; color: var(--warn); }

/* Settings > Sounds: a name, a level and a test for each sound. */
.sound-rows { display: grid; gap: 10px; }
.sound-row { display: grid; grid-template-columns: 150px minmax(0, 1fr) 64px; align-items: center; gap: 12px; }
.sound-name { font-weight: 600; }
.sound-vol { width: 100%; accent-color: var(--accent); }
@media (max-width: 480px) {
  .sound-row { grid-template-columns: minmax(0, 1fr) 64px; }
  .sound-row .sound-vol { grid-column: 1 / -1; grid-row: 2; }
}
