/* Trip Light customer portal.
 *
 * One stylesheet, no framework. The whole front end is seven pages of forms and
 * a list; a build step to produce that would cost more than it saves.
 */

/* The cover a page opens under until this stylesheet is in (app.py,
   LOAD_HEAD): gone in the very frame the page is styled, so it is only ever
   seen on a page painted before its styles arrived - never between two
   pages. */
#tlLoad { display: none !important; }

:root {
  --bg: #0d1013;
  --card: #161b20;
  --card-2: #1e252c;
  --line: #2a323a;
  --text: #e7edf3;
  --muted: #8c99a6;
  --accent: #35c1e8;
  --on-accent: #05222b;      /* text on the accent: buttons, lit toggles, the current tab */
  --ok: #3fb96a;
  --warn: #d9a441;
  --bad: #d9534f;
  --radius: 10px;
  /* The menu bar and the content boxes: the animated background shows
     through them. */
  --glass: rgba(0, 0, 0, 0.6);
  /* The header's glass: denser than --glass, as nothing is blurred behind it
     (see header::before). */
  --glass-bar: rgba(9, 11, 14, 0.86);

  /* Two names that were used across this file and never defined.
     --panel appears on the pickers and now on the control panel, --fg on
     most of its text. An undefined custom property makes the whole
     declaration invalid, so those elements had no background and inherited
     their colour - which nobody noticed while they sat on a card, and which
     showed up the moment the control panel became a full-screen sheet and
     the dashboard was visible straight through it.

     Defined as aliases rather than renamed at every use: one place to
     change, and no chance of missing one. */
  --panel: var(--card);
  --fg: var(--text);
}

/* Light mode (Account settings, Appearance): the same variables, light. The
   accent is a deeper cyan here - the bright one reads poorly as text on
   white. */
:root[data-theme="light"] {
  --bg: #eef1f4;
  --card: #ffffff;
  --card-2: #f1f4f7;
  --line: #d3dae1;
  --text: #1b232b;
  --muted: #56626e;
  --accent: #1583a6;
  --on-accent: #ffffff;
  --glass: rgba(255, 255, 255, 0.6);
  --glass-bar: rgba(246, 248, 250, 0.9);
}
:root[data-theme="light"] h1 { text-shadow: 0 1px 3px rgba(255, 255, 255, 0.8); }

* { box-sizing: border-box; }

/* Scrollbars: thin, a soft rounded thumb on no track, in the theme's own
   muted colour. Firefox takes the standard properties (scrollbar-width does
   not inherit, hence every element). Chrome, Edge and Safari take the
   -webkit- ones - with no arrow buttons, which only those can drop - and
   Chrome ignores them wherever a standard one is set, so those are
   Firefox's alone. */
@supports (-moz-appearance: none) {
  :root { scrollbar-color: color-mix(in srgb, var(--muted) 45%, transparent) transparent; }
  * { scrollbar-width: thin; }
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-button { display: none; }
::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--muted) 45%, transparent) padding-box;
  border: 3px solid transparent; border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover { background-color: color-mix(in srgb, var(--muted) 75%, transparent); }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

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

/* ---- chrome ------------------------------------------------------------ */
header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--line);
  /* Sticky: the menu stays at the top while the page scrolls under it, on
     a layer of its own behind it (::before). Dense glass rather than a blur
     of what passes beneath: Firefox blurs a backdrop again every time
     anything on the page changes - each frame of a lamp preview, each of
     the background's - and on a busy page that was most of the page's GPU
     time, for a blur of a background that is soft already. */
  position: sticky;
  top: 0;
  z-index: 40;
}
header::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--glass-bar);
}

.brand {
  font-weight: 650;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
  text-decoration: none;
  font-size: 14px;
}

nav { display: flex; align-items: center; gap: 16px; }
nav a { color: var(--muted); text-decoration: none; font-size: 14px; }
nav a:hover { color: var(--text); }
.inline { display: inline; margin: 0; }

main {
  max-width: 460px;
  margin: 0 auto;
  padding: 28px 16px 64px;
}
main.wide { max-width: 760px; }

h1 { font-size: 22px; margin: 0 0 18px; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); }

/* Text straight on the animated background gets a backing like the boxes':
   the background takes the default group's palette and can be any colour -
   bright enough, grey text on it would be lost. */
.lview, main > p.muted {
  width: fit-content; padding: 6px 12px; border-radius: 8px; background: var(--glass);
}
h2 { font-size: 15px; margin: 0 0 12px; letter-spacing: 0.02em; }

.muted { color: var(--muted); font-size: 14px; }
.muted.empty { padding: 8px 0; }

/* ---- cards and forms --------------------------------------------------- */
.card {
  background: var(--glass);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px;
  margin: 0 0 18px;
}

form { margin: 14px 0 0; }
form:first-of-type { margin-top: 0; }

label {
  display: block;
  margin: 0 0 14px;
  font-size: 14px;
  color: var(--muted);
}
label small { display: block; margin-top: 4px; font-size: 12px; color: var(--muted); }

input[type=text], input[type=password], input:not([type]), select {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 9px 11px;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--text);
  font: inherit;
}
input:focus, select:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

/* A row of controls that belong to one action - add a light, rename a group.
   Wraps rather than scrolling, because the narrow case is a phone. */
.row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.row input, .row select { width: auto; flex: 1 1 140px; margin-top: 0; }
.row button { flex: 0 0 auto; }

button {
  padding: 9px 16px;
  background: var(--accent);
  border: 0;
  border-radius: 7px;
  color: var(--on-accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
button:hover { filter: brightness(1.08); }

button.link {
  background: none;
  color: var(--muted);
  padding: 4px 0;
  font-weight: 400;
}
button.link:hover { color: var(--text); filter: none; }
button.danger, button.link.danger { color: var(--bad); }
button.danger { background: none; border: 1px solid var(--bad); }
/* A .link button is meant to read as text, so it carries no side padding. A
   .danger one draws a border around that text, and 'Remove from account' with
   its border touching the R and the t reads as a rendering fault rather than a
   button. Put the sides back for the bordered case only. */
button.link.danger { padding: 4px 12px; border-radius: 6px; }

/* ---- the unlink confirmation ------------------------------------------- */
dialog.ask {
  max-width: 420px;
  padding: 20px 22px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
  color: var(--text);
}
dialog.ask::backdrop { background: rgba(0, 0, 0, 0.55); }

/* "Add a light": the camera button and its scanner (qrscan.js) - the button
   only where there is a camera to use (tl-camera, on the page's root). */
.row .qrbtn { display: none; align-items: center; justify-content: center; padding: 8px 11px;
              align-self: stretch; }
.tl-camera .row .qrbtn { display: inline-flex; }
dialog.qrdlg { width: min(400px, calc(100vw - 32px)); }
dialog.qrdlg h3 { margin: 0 0 12px; }
.qrview { position: relative; width: 100%; aspect-ratio: 1; overflow: hidden;
          border-radius: 10px; background: #000; }
.qrview video { width: 100%; height: 100%; object-fit: cover; display: block; }
.qrframe { position: absolute; inset: 18%; border: 2px solid rgba(255, 255, 255, .85);
           border-radius: 12px; box-shadow: 0 0 0 999px rgba(0, 0, 0, .28); pointer-events: none; }
.qrmsg { margin: 10px 0 4px; min-height: 2.6em; }
input.scanned { outline: 2px solid var(--accent, #2b7); outline-offset: 1px; transition: outline-color .4s; }
/* ... and the question it ends in, as the one a QR code's link does (adopt.js) */
.adoptdlg .field { display: grid; gap: 4px; font-size: 12.5px; color: var(--muted); }
.adoptdlg .field[hidden] { display: none; }
.adoptdlg .field select { margin: 0; }
.adoptdlg .adoptmsg { margin: 0; }
.adoptdlg .tlm-text code { font-size: 13.5px; color: var(--text); }
.adoptdlg .tlm-acts .tlm-yes { min-width: 96px; }

/* Every dialog comes in and goes out like the other modals: site.js keeps
   one up with .closing until its way out has played. */
dialog[open] { animation: m-pop-in .22s cubic-bezier(.2, .8, .2, 1); }
dialog[open]::backdrop { animation: m-fade-in .22s ease-out; }
dialog.closing { animation: m-pop-out .18s ease-in forwards; }
dialog.closing::backdrop { animation: m-fade-out .18s ease-in forwards; }
@media (prefers-reduced-motion: reduce) {
  dialog[open] { animation: m-fade-in .2s ease-out; }
  dialog.closing { animation: m-fade-out .16s ease-in forwards; }
}
/* tlAlert and tlConfirm (site.js): the page's own alert and confirm. The
   card is .tlm-body, so a click on the dialog itself is a click beside it. */
dialog.tlmodal {
  width: min(440px, calc(100vw - 32px)); padding: 0;
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--card); color: var(--text);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
}
dialog.tlmodal::backdrop { background: rgba(0, 0, 0, .6); }
.tlm-body { display: grid; gap: 12px; padding: 20px 22px 18px; }
.tlm-title { margin: 0; font-size: 17px; }
.tlm-title[hidden] { display: none; }
.tlm-text { margin: 0; font-size: 14.5px; line-height: 1.55; white-space: pre-line;
            overflow-wrap: anywhere; max-height: 60vh; overflow-y: auto; }
.tlm-acts { display: flex; justify-content: flex-end; align-items: center; gap: 14px; margin-top: 6px; }
.tlm-acts button[hidden] { display: none; }
.tlm-acts .tlm-yes { min-width: 84px; }
.tlm-acts .tlm-yes.danger { background: var(--bad); border: 0; color: #fff; }
dialog.ask h3 { margin: 0 0 10px; font-size: 18px; }
dialog.ask p  { margin: 0 0 16px; color: var(--muted); font-size: 14px; line-height: 1.55; }
/* The acknowledgement is the point of the dialog, so it reads as text to agree
   with rather than as another control to skip past. */
dialog.ask .ack {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  margin: 0 0 18px;
  font-size: 14px;
  cursor: pointer;
}
dialog.ask .ack input { margin: 2px 0 0; flex: none; }
dialog.ask .row.end { display: flex; gap: 10px; justify-content: flex-end; }
dialog.ask button:disabled { opacity: 0.45; cursor: not-allowed; }

/* ---- notices ----------------------------------------------------------- */
.notice {
  margin: 0 0 16px;
  padding: 10px 13px;
  border-radius: 7px;
  border: 1px solid var(--bad);
  background: rgba(217, 83, 79, 0.1);
  font-size: 14px;
}
.notice.ok { border-color: var(--ok); background: rgba(63, 185, 106, 0.1); }

/* ---- lights ------------------------------------------------------------- */
ul.lights { list-style: none; margin: 0; padding: 0; }

/* A light's row, as a grid (see "a light's row" further down for why). */
.light {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "head";
  align-items: center;
  column-gap: 10px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.light > .lhead { grid-area: head; }
.light > .lctl { grid-area: ctl; }
.light > .lpv { grid-area: pv; }
.light:first-child { border-top: 0; }
.light-name { font-weight: 500; }
.light .state { color: var(--muted); font-size: 13px; margin-left: auto; }

/* The cell, next to online/offline rather than given a row of its own: it is
   the same kind of fact about the lamp, and the row is already crowded. It
   stays quiet until the charge is low enough to be worth reading. */
.light .batt { color: var(--ok); display: inline-flex; align-items: center; }
.battsvg { width: 30px; height: 14px; overflow: visible; }
.battsvg .shell { fill: none; stroke: currentColor; stroke-width: 1.2; opacity: .55; }
.battsvg .nub   { fill: currentColor; opacity: .55; }
.battsvg .seg   { fill: currentColor; }
.battsvg .seg.off { opacity: .14; }
.light .batt.low  { color: var(--warn); }
/* WiFi: grey until lit - see wifi_markup() in app.py. */
.light .wifi { display: inline-flex; align-items: center; }
.wifisvg { width: 20px; height: 16px; display: block; }
.wifisvg circle { fill: var(--muted); opacity: .45; }
.wifisvg path { fill: none; stroke: var(--muted); stroke-width: 2; stroke-linecap: round; opacity: .45; }
.wifisvg.poor circle.on { fill: var(--bad); opacity: 1; }
.wifisvg.fair circle.on { fill: var(--warn); opacity: 1; }
.wifisvg.fair path.on   { stroke: var(--warn); opacity: 1; }
.wifisvg.good circle.on { fill: var(--ok); opacity: 1; }
.wifisvg.good path.on   { stroke: var(--ok); opacity: 1; }
/* Offline: the signal grey, crossed out in red. */
.wifisvg.off path.wx { stroke: var(--bad); stroke-width: 2.4; opacity: 1; }
.light .batt.crit { color: var(--bad); font-weight: 600; }
.light .batt[hidden] { display: none; }

/* The status light. Grey means we have never heard from this light at all,
   which is a different thing from having heard it say goodbye. */
.dot {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--muted);
  flex: 0 0 auto;
}
.dot.online { background: var(--ok); box-shadow: 0 0 7px rgba(63, 185, 106, 0.7); }
.dot.offline { background: var(--bad); }
.dot.unknown { background: var(--line); border: 1px solid var(--muted); }

/* The microphone, left of the battery, for the lights that have one. */
.light .mic { color: var(--muted); display: inline-flex; align-items: center; }
.light .mic[hidden] { display: none; }
.micsvg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8;
          stroke-linecap: round; stroke-linejoin: round; }

details.edit { width: 100%; }
details.edit summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 13px;
  padding: 2px 0;
  list-style: none;
}
details.edit summary::-webkit-details-marker { display: none; }
details.edit summary::before { content: "› "; }
details.edit[open] summary::before { content: "⌄ "; }
details.edit summary:hover { color: var(--text); }
details.edit .row { margin: 10px 0; }
details.group-edit { margin: -4px 0 10px; }

/* ---- TOTP setup -------------------------------------------------------- */
.qr {
  display: block;
  width: 190px; height: 190px;
  margin: 14px 0;
  background: #fff;
  border-radius: 8px;
  padding: 8px;
}
.secret {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 15px;
  letter-spacing: 0.08em;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 10px 12px;
  word-break: break-all;
  user-select: all;
}

/* ---- light control ------------------------------------------------------ */
/* Its own line under the name. Side by side works on a desktop and falls
   apart on a phone, which is where a light actually gets switched off. */
.ctl {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 100%;
  margin: 8px 0 0;
}

.pwr {
  width: 34px; height: 34px;
  padding: 0;
  flex: 0 0 auto;
  font-size: 15px;
  line-height: 1;
  background: var(--card-2);
  border: 1px solid var(--line);
  color: var(--muted);
}
.pwr svg { display: block; margin: auto; }
.pwr.on {
  background: var(--ok);
  border-color: var(--ok);
  color: #04220f;
}
.pwr:disabled, .bri:disabled, .set:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.pwr:disabled:hover { filter: none; }

.bri {
  flex: 1 1 120px;
  width: auto;
  margin: 0;
  padding: 0;
  height: 22px;
  background: none;
  border: 0;
  accent-color: var(--accent);
}

/* The slider applies on release once the script is running, so the button it
   needs in a no-JavaScript browser only gets in the way here. */
.ctl.js .set { display: none; }

/* A control whose command has been sent but not yet confirmed by the light's
   next MQTT publish. Better than a spinner: it says "heard you" without
   claiming the light has actually done it. */
.ctl.busy { opacity: 0.55; }

/* ---- captcha ----------------------------------------------------------- */
.captcha {
  display: block;
  margin: 8px 0 8px;
  width: 250px;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: 7px;
  image-rendering: pixelated;
}

/* The one card that destroys something. Outlined rather than filled: it has to
   read as different from the forms above it without shouting on every visit. */
.danger-zone { border-color: rgba(217, 83, 79, 0.45); }
.danger-zone h2 { color: var(--bad); }

/* ---------------------------------------------------------- effect & sync */
/* A second disclosure under each light, next to Edit. Kept closed by default:
   most visits are "turn it on" or "make it dimmer", and a row that opens with
   three dropdowns showing pushes the next light off the screen. */
.scene > summary { cursor: pointer; font-size: 13px; opacity: .8; }
.scene > summary::marker { color: var(--muted); }
.scene-body { display: grid; gap: 8px; padding: 8px 0 4px; }

.pickform { margin: 0; }
.pick { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.pick > span { flex: 0 0 5.5em; color: var(--muted); }
.pick select {
  flex: 1; min-width: 0; padding: 5px 8px; border-radius: 7px;
  border: 1px solid var(--line); background: var(--panel); color: var(--fg);
  font: inherit; font-size: 13px;
}
.pick.muted select { opacity: .55; }

/* The effect's own controls. Which of these exist depends on the effect, so
   the block is rendered per light and rebuilt when the effect changes - see
   _fx_controls(). Laid out like the pickers above them so the scene block
   reads as one column of settings rather than two kinds of control. */
.fxknobs { display: flex; flex-direction: column; gap: 6px; }
.fxform { margin: 0; }
.fxslide { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.fxslide > span { flex: 0 0 5.5em; color: var(--muted); }
.fxrange { flex: 1; min-width: 0; accent-color: var(--accent); cursor: pointer; }
.fxrange:disabled { opacity: .45; cursor: default; }
.fxopt { padding-left: calc(5.5em + 8px); }

.switches { display: flex; flex-wrap: wrap; gap: 14px; padding-top: 2px; }
.syncform { margin: 0; }
.sw { display: flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; }
.sw input { cursor: pointer; }

/* The group switch sits up with the group's name rather than down among the
   lights, because it acts on all of them. */
.group > h2 { display: inline-block; margin-right: 12px; }
.groupsync { display: inline-flex; vertical-align: middle; }
.group.synced { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }

.tag.cloud {
  margin-left: 8px; font-size: 11px; padding: 1px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

/* Anything mid-flight is dimmed and unclickable, so a second click cannot
   queue a second publish while the first is still settling. */
.pickform.busy, .syncform.busy { opacity: .5; pointer-events: none; }

/* ---------------------------------------------------- leader and followers */
.lead { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; margin-left: 10px; }
.lead > span { color: var(--muted); }
.lead select {
  padding: 4px 7px; border-radius: 7px; border: 1px solid var(--line);
  background: var(--panel); color: var(--fg); font: inherit; font-size: 13px;
}
.lead.muted { opacity: .5; }

/* A follower is dimmed, not hidden: it is still in the room and still worth
   seeing the colour of. What it is not is somewhere to press anything. */
.light.is-follower { opacity: .72; }
.light.is-follower .ctl, .light.is-follower .pickform { pointer-events: none; }
.light.is-master .light-name { font-weight: 650; }
.light.is-master .dot { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); }
.tag.warn { background: color-mix(in srgb, #d29922 28%, transparent); }

/* A switch that is not this light's to move while it follows a cloud group.
   Dimmed with a cursor that says so, rather than removed: it still reports
   the light's actual setting, and hiding it would leave the owner wondering
   whether local sync is on. */
.sw.locked { opacity: .5; cursor: not-allowed; }
.sw.locked input { cursor: not-allowed; }
.audiomode .pick > span { flex: 0 0 5.5em; }

/* ==========================================================================
   The full control panel, one light at a time.

   Laid out like WLED's own page on purpose - the same order, the same list
   rows with the palette gradient behind the name, the same tab strip along
   the bottom - so that somebody who already knows the lamp's own UI is not
   learning a second one. It borrows this dashboard's colour tokens rather
   than WLED's, so it still looks like part of the portal.
   ========================================================================== */
.tlc {
  position: fixed; inset: 0; z-index: 50;
  /* The dashboard behind stays legible as context but stops competing for
     attention - dimmed rather than blurred: the panel's lamp redraws it 42
     times a second, and a full-screen blur done again each time (Firefox
     does) cost more than everything else on the page. */
  background: rgba(0, 0, 0, .68);
  display: flex; align-items: stretch; justify-content: center;
}
.tlc.hide { display: none; }
body.tlc-open { overflow: hidden; }

/* Phone first: the panel IS the screen. 100dvh rather than 100vh so the
   browser's own chrome sliding in and out does not cut the bottom off. */
.tlc-sheet {
  position: relative;
  width: 100%; height: 100dvh;
  display: flex; flex-direction: column;
  background: var(--panel); color: var(--fg);
  overflow: hidden;
}
@supports not (height: 100dvh) { .tlc-sheet { height: 100vh; } }

/* As the screen it slides up from the bottom edge each time it opens, the
   dim backdrop fading in behind it; as the card (below) it grows into place
   instead. Opening is display:none -> flex, which starts CSS animations
   afresh every time, so no script is involved.

   Closing goes the way it came: close() adds .closing and hides the panel
   when the sheet's animation ends - at once wherever there is none, which it
   tells from the computed animation name. */
@keyframes tlc-up     { from { transform: translateY(100%); } to { transform: none; } }
@keyframes tlc-dim    { from { background-color: transparent; } }
@keyframes tlc-down   { from { transform: none; } to { transform: translateY(100%); } }
@keyframes tlc-undim  { to { background-color: transparent; } }
/* Every card-like modal - the panel as a card, the colour picker as one,
   the dialogs - comes in and goes out the same way. */
@keyframes m-pop-in   { from { opacity: 0; transform: translateY(10px) scale(.96); } }
@keyframes m-pop-out  { to { opacity: 0; transform: translateY(10px) scale(.96); } }
@keyframes m-fade-in  { from { opacity: 0; } }
@keyframes m-fade-out { to { opacity: 0; } }
.tlc { animation: tlc-dim .28s ease-out; }
.tlc-sheet { animation: tlc-up .32s cubic-bezier(.2, .8, .2, 1); }
.tlc.closing { animation: tlc-undim .24s ease-in forwards; }
.tlc.closing .tlc-sheet { animation: tlc-down .24s cubic-bezier(.4, 0, 1, 1) forwards; }

/* With room to spare it becomes a card, centred, with the dashboard blurred
   behind it. A fixed height, so switching tabs does not resize it.

   Room to spare means a mouse (a desktop window, however short), or a
   screen that is big both ways up - a tablet. Not width alone: a phone can
   report 630 CSS px across upright (small display-size setting), and on its
   side it is wide but only ~400 high; both are phones and get the screen. */
@media (min-width: 620px) and (hover: hover) and (pointer: fine),
       (min-width: 700px) and (min-height: 700px) {
  /* As tall as the window allows, 5 mm clear above and below, and as wide
     as the page's content (main.wide: 760px less its 16px each side), its
     edges on the content's. */
  .tlc { align-items: center; padding: 5mm 16px; animation-duration: .22s; }
  .tlc-sheet {
    width: min(728px, 100%); height: 100%;
    border: 1px solid var(--line); border-radius: 16px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
    animation: m-pop-in .24s cubic-bezier(.2, .8, .2, 1);
  }
  .tlc.closing { animation-duration: .18s; }
  .tlc.closing .tlc-sheet { animation: m-pop-out .18s ease-in forwards; }
}
/* With reduced motion nothing moves: the sheet only fades in and out. */
@media (prefers-reduced-motion: reduce) {
  .tlc-sheet { animation: m-fade-in .2s ease-out; }
  .tlc.closing .tlc-sheet { animation: m-fade-out .16s ease-in forwards; }
}

.tlc-top { display: flex; align-items: center; gap: 12px; padding: 8px 10px 8px 14px; }
.tlc-name { font-weight: 650; font-size: 15px; flex: 0 1 auto; max-width: 40%; min-width: 0;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tlc-x {
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  background: var(--card-2); border: 1px solid var(--line); color: var(--muted);
  font-size: 19px; line-height: 1; cursor: pointer; padding: 0;
}
.tlc-x:hover { color: var(--fg); border-color: var(--accent); }
/* Identify, beside the name (a single light), and in a light's row. */
.tlc-ident, .identbtn {
  width: 28px; height: 28px; border-radius: 50%; flex: none; padding: 4px;
  background: none; border: 1px solid var(--line); color: var(--muted); cursor: pointer;
}
.tlc-ident svg, .identbtn svg { width: 100%; height: 100%; display: block; fill: none;
  stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.tlc-ident:hover, .identbtn:hover { color: var(--fg); border-color: var(--accent); filter: none; }
.tlc-ident:disabled, .identbtn:disabled { opacity: .5; }
.tlc-ident.hide, .identbtn[hidden] { display: none; }
.identbtn { width: 24px; height: 24px; padding: 3px; }
/* The runtime target (control.js, drawRt): what is set, and the chooser. */
/* An ESP32-TL light's segments (control.js drawSegs): All or one part, and
   that part's own power and brightness. Chips like the effect filters. */
.tlc-segs { padding: 0 14px 8px; }
.tlc-segs.hide { display: none; }
.tlc-segrow { display: flex; flex-wrap: wrap; gap: 5px; }
.tlc-seg {
  display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 0 12px;
  border-radius: 999px; border: 1px solid var(--line); background: var(--bg); color: var(--muted);
  font: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
}
.tlc-seg:hover { color: var(--fg); filter: none; }
.tlc-seg.on {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  border-color: var(--accent); color: var(--fg);
}
.tlc-seg.off { opacity: .55; }
.tlc-seg small { font-size: 11px; opacity: .8; }
.tlc-segnote { margin: 8px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.tlc-seg i { width: 10px; height: 10px; border-radius: 50%; box-shadow: 0 0 0 1px #0006; }
.tlc-segctl { display: flex; align-items: center; gap: 10px; margin-top: 8px; font-size: 13px; color: var(--muted); }
.tlc-segpow { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; }
.tlc-segctl input[type=range] { flex: 1; min-width: 0; }
.tlc-rt { padding: 0 14px 8px; font-size: 13px; color: var(--muted); }
.tlc-rt.hide { display: none; }
.tlc-rtnow { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; }
.tlc-rtnow strong { color: var(--fg); }
.tlc-rtnow .link { font-size: 13px; padding: 0; }
.tlc-rtwarn { color: var(--warn); }
.tlc-rtpick { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; }
.tlc-rtat { display: inline-flex; align-items: center; gap: 6px; }
.tlc-rtat input { width: auto; margin: 0; padding: 4px 6px; font-size: 13px; }
.tlc-rtwhen { color: var(--fg); font-weight: 600; }
.tlc-rtwhen.bad { color: var(--warn); }
.tlc-rtgo { padding: 5px 14px; font-size: 13px; }
.tlc-rt .tlc-hint { margin: 6px 0 0; }
/* The palette designer's From a photo. */
.pd-photo { margin: -4px 0 10px; }
.pd-photo .link { font-size: 13px; padding: 0; color: var(--accent); }

/* Tabs at the top, as a segmented control: what the panel is showing is the
   first thing to know. */
.tlc-tabs {
  display: flex; gap: 3px; margin: 0 12px 8px; padding: 3px;
  background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
}
.tlc-tabbtn {
  flex: 1; min-height: 32px; padding: 0 6px; border: 0; border-radius: 8px;
  background: none; color: var(--muted); cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 600;
}
.tlc-tabbtn:hover { color: var(--fg); filter: none; }
.tlc-tabbtn.on { background: var(--card-2); color: var(--fg);
                 box-shadow: inset 0 0 0 1px var(--line); }


/* The one scrolling area. Lists grow with their content inside it rather than
   scrolling on their own - a list scrolling inside a page that also scrolls
   is where a thumb loses its place. */
.tlc-body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
            padding: 0 12px calc(12px + env(safe-area-inset-bottom, 0px)); }
.tlc-tab.hide { display: none; }
.tlc-hint { margin: 0 0 12px; font-size: 13px; color: var(--muted); }
.tlc-hint.hide { display: none; }

/* ---- colours: three tiles; the wheel is behind them --------------------- */
.tlc-slots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin: 2px 0 10px; }
.tlc-slot {
  display: flex; flex-direction: column; align-items: stretch; gap: 6px;
  padding: 6px; border-radius: 10px; cursor: pointer; text-align: left;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
  font: inherit; font-weight: 400;
}
.tlc-slot:hover { border-color: var(--accent); filter: none; }
.tlc-slot.idle { opacity: .5; }
.tlc-sw {
  display: block; height: 34px; border-radius: 7px;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent);
}
.tlc-stext { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.tlc-sname { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tlc-shex { font-size: 11.5px; color: var(--muted); font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }

/* ---- search and lists ------------------------------------------------------ */
.tlc-find {
  position: sticky; top: 0; z-index: 2;
  background: var(--panel); padding: 2px 0 8px;
}
.tlc-find input {
  width: 100%; margin: 0; padding: 8px 10px; border-radius: 9px; box-sizing: border-box;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
  font: inherit; font-size: 14px;
}
/* The search and the list's order, side by side. */
.tlc-frow { display: flex; gap: 6px; align-items: stretch; }
.tlc-frow input { flex: 1 1 auto; min-width: 0; }
.tlc-sort {
  flex: 0 0 auto; width: auto; margin: 0; padding: 0 8px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
  font: inherit; font-size: 13px;
}
.tlc-list { display: grid; gap: 2px; }
.tlc-item {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 38px;
  padding: 6px 10px; border-radius: 8px; border: 1px solid transparent;
  background: none; color: var(--fg); cursor: pointer; text-align: left;
  font: inherit; font-size: 14.5px; font-weight: 400;
}
.tlc-item:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); filter: none; }
.tlc-item.on {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
}
.tlc-grad {
  flex: 0 0 58px; height: 18px; border-radius: 5px;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent);
}
.tlc-iname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Effect types: WLED's icons, as chips under the search to filter by and at
   the end of each effect's row. An icon that is not filtering is grey, as in
   WLED's own filter row. */
.tlc-flt { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.tlc-fchip {
  display: inline-flex; align-items: center; gap: 5px; min-height: 28px; padding: 0 10px;
  border-radius: 999px; border: 1px solid var(--line); background: var(--bg); color: var(--muted);
  font: inherit; font-size: 12.5px; font-weight: 500; cursor: pointer;
}
.tlc-fchip:hover { color: var(--fg); filter: none; }
.tlc-anim { margin-left: auto; }
.tlc-fchip[aria-pressed="true"] {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  border-color: var(--accent); color: var(--fg);
}
.tlc-fic { font-size: 14px; line-height: 1; }
.tlc-fchip:not([aria-pressed="true"]) .tlc-fic { filter: grayscale(100%); }
.tlc-ficons { flex: none; color: var(--muted); font-size: 14px; letter-spacing: 3px; }
/* The cube icon of the 3D type (control.js, CUBE_ICON), the size of the glyphs beside it. */
.tlc-fic svg, .tlc-ficons svg { width: 1em; height: 1em; vertical-align: -0.14em; fill: none; stroke: currentColor;
                                stroke-width: 1.5; stroke-linejoin: round; }
.tlc-fchip[aria-pressed="true"] .tlc-fic svg { stroke: var(--accent); }
.tlc-empty { padding: 14px 4px; color: var(--muted); font-size: 14px; margin: 0; }

/* ---- the effect that is playing, and its own controls ------------------- */
/* A footer on the Effects tab, fixed under the list (control.js, showTab).
   Sliders one to a row - two where the panel is wide - and the on/off
   options side by side. */
.tlc-fxfoot {
  max-height: 45%; overflow-y: auto; overscroll-behavior: contain;
  padding: 8px 14px calc(10px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--line); background: var(--panel);
  box-shadow: 0 -8px 18px rgba(0, 0, 0, .18);
}
.tlc-fxfoot.hide { display: none; }
.tlc-fxfoot { display: flex; align-items: center; gap: 12px; }
.tlc-fxfoot .tlc-now { flex: 1 1 auto; min-width: 0; }
.tlc-fxfoot.nokn .tlc-knobs { display: none; }
.tlc-fxfoot.nokn .tlc-nowname { margin-bottom: 0; }
/* The light as it looks now (fxpreview.js). */
.tlc-pv { flex: 0 0 42px; height: 84px; }
.tlc-pv.hide { display: none; }
.tlc-fxfoot.nokn .tlc-pv { flex-basis: 26px; height: 52px; }
/* Each effect in its own small strip, in the light's colours. */
.tlc-fxpv { flex: 0 0 36px; height: 10px; border-radius: 5px; background: var(--bg);
            box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); }
.tlc-nowname { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; min-width: 0; }
.tlc-nowk { font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); flex: none; }
#tlcNowName { font-weight: 650; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tlc-knobs { display: flex; flex-wrap: wrap; gap: 2px 18px; }
.tlc-knobs .tlc-hint { margin: 0; }
.tlc-knob { flex: 1 1 280px; display: grid; grid-template-columns: 6.5em 1fr; align-items: center; gap: 10px; font-size: 12.5px; }
.tlc-knob > span { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tlc-knob input { width: 100%; margin: 0; accent-color: var(--accent); min-height: 26px; }
.tlc-knob select { width: 100%; margin: 0; padding: 5px 8px; font-size: 13px; }
.tlc-opt { flex: 0 0 auto; display: flex; align-items: center; gap: 7px; font-size: 13px; min-height: 28px; }
.tlc-opt input { width: 17px; height: 17px; margin: 0; accent-color: var(--accent); }

/* ---- the effect designer (control.js, openDesign) -----------------------
   The panel editing a saved preset: a large lamp showing it and a Save /
   Cancel bar, neither of which a light's panel has. On a phone the lamp
   sits between the header and the tabs; with room, it takes a column of
   its own beside the controls and the panel grows wider to make room. */
.tlc-stage, .tlc-dbar { display: none; }
.tlc.design .tlc-stage {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  flex: none; height: clamp(170px, 31dvh, 290px); padding: 6px 12px 10px;
  background: radial-gradient(ellipse 60% 70% at 50% 58%,
              color-mix(in srgb, var(--fg) 7%, transparent), transparent 72%);
}
.tlc-biglamp { flex: 1 1 auto; min-height: 0; height: 100%; aspect-ratio: 254 / 474; }
/* Show on a light, under the lamp. */
.tlc-onlight { flex: none; display: flex; align-items: center; gap: 8px; max-width: 100%;
               font-size: 12.5px; color: var(--muted); }
.tlc-onlight span { flex: none; }
.tlc-onlight select { width: auto; min-width: 0; max-width: 220px; margin: 0; padding: 5px 8px; font-size: 13px; }
.tlc.design .tlc-pv { display: none; }
/* Presets carry no brightness: its place in the header stays, empty. */
.tlc.design .tlc-bri { visibility: hidden; }
.tlc.design .tlc-tabbtn[data-go="2"] { display: none; }
.tlc.design .tlc-dbar {
  display: flex; align-items: center; justify-content: flex-end; gap: 14px; flex: none;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--line); background: var(--panel);
}
.tlc-dstate { margin-right: auto; font-size: 13px; color: var(--warn); }
/* A new preset: its name, where the "unsaved" note would be. */
.tlc .tlc-dname { display: none; }       /* over the page's input[type=text] */
.tlc.dnew .tlc-dname { display: block; flex: 1 1 auto; width: auto; min-width: 0; max-width: 360px;
                       margin: 0 auto 0 0; }
.tlc.dnew .tlc-dstate { display: none; }
.tlc-dbar #tlcDSave:disabled { opacity: .45; cursor: default; filter: none; }
/* The designer's footer carries no lamp, and the bar under it already has
   the safe-area padding. */
.tlc.design .tlc-fxfoot { padding-bottom: 10px; }
@media (min-width: 620px) and (hover: hover) and (pointer: fine),
       (min-width: 700px) and (min-height: 700px) {
  .tlc.design .tlc-sheet {
    width: min(1040px, 100%);
    display: grid;
    grid-template-columns: minmax(220px, 34%) minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr) auto auto;
    grid-template-areas: "top top" "stage tabs" "stage body" "stage foot" "bar bar";
  }
  .tlc.design .tlc-top { grid-area: top; }
  .tlc.design .tlc-stage { grid-area: stage; height: auto; min-height: 0; padding: 18px 12px 24px;
                           border-right: 1px solid var(--line); }
  .tlc.design .tlc-biglamp { height: auto; max-height: 520px; max-width: 100%; }
  .tlc.design .tlc-onlight { flex-direction: column; align-items: stretch; gap: 4px; width: 100%; max-width: 240px; }
  .tlc.design .tlc-onlight select { max-width: none; width: 100%; }
  .tlc.design .tlc-tabs { grid-area: tabs; margin-top: 10px; }
  .tlc.design .tlc-body { grid-area: body; }
  .tlc.design .tlc-fxfoot { grid-area: foot; max-height: none; }
  .tlc.design .tlc-dbar { grid-area: bar; }
}

/* ---- presets, as buttons ----------------------------------------------- */
.tlc-presets { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 6px; padding-top: 2px; }
.tlc-preset {
  min-height: 44px; padding: 8px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
  font: inherit; font-size: 14px; font-weight: 600;
}
.tlc-preset:hover { border-color: var(--accent); filter: none; }
.tlc-preset.on { background: color-mix(in srgb, var(--accent) 22%, transparent); border-color: var(--accent); }

/* ---- brightness, in the header ------------------------------------------ */
.tlc-bri { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px; }
.tlc-sun { width: 16px; height: 16px; flex: none; fill: none; stroke: var(--muted);
           stroke-width: 2; stroke-linecap: round; }
.tlc-bri input { flex: 1; min-width: 0; margin: 0; min-height: 28px; accent-color: var(--accent); }
.tlc-briv { flex: 0 0 2.8em; text-align: right; font-size: 12.5px; color: var(--muted);
            font-variant-numeric: tabular-nums; }

/* ---- the colour picker, over the panel ---------------------------------- */
.tlc-picker {
  position: absolute; inset: 0; z-index: 5;
  background: rgba(0, 0, 0, .45);
  display: flex; align-items: flex-end; justify-content: center;
}
.tlc-picker.hide { display: none; }
.tlc-pcard {
  width: 100%; max-height: 100%; overflow-y: auto;
  display: grid; gap: 14px; justify-items: stretch;
  padding: 14px 16px calc(16px + env(safe-area-inset-bottom, 0px));
  background: var(--card); border-top: 1px solid var(--line);
  border-radius: 16px 16px 0 0; box-shadow: 0 -12px 30px rgba(0, 0, 0, .45);
}
/* In and out like the panel: .closing while it goes (control.js,
   dismissPicker). */
.tlc-picker { animation: m-fade-in .22s ease-out; }
.tlc-pcard { animation: tlc-up .28s cubic-bezier(.2, .8, .2, 1); }
.tlc-picker.closing { animation: m-fade-out .2s ease-in forwards; }
.tlc-picker.closing .tlc-pcard { animation: tlc-down .2s cubic-bezier(.4, 0, 1, 1) forwards; }
/* The same "room to spare" as the panel itself (above). */
@media (min-width: 620px) and (hover: hover) and (pointer: fine),
       (min-width: 700px) and (min-height: 700px) {
  .tlc-picker { align-items: center; padding: 20px; }
  .tlc-pcard { width: min(340px, 100%); border: 1px solid var(--line); border-radius: 16px;
               animation: m-pop-in .22s cubic-bezier(.2, .8, .2, 1); }
  .tlc-picker.closing .tlc-pcard { animation: m-pop-out .18s ease-in forwards; }
}
@media (prefers-reduced-motion: reduce) {
  .tlc-pcard { animation: none; }
  .tlc-picker.closing .tlc-pcard { animation: m-fade-out .2s ease-in forwards; }
}
.tlc-phead { display: flex; align-items: center; gap: 10px; }
.tlc-psw {
  width: 28px; height: 28px; border-radius: 8px; flex: none;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 20%, transparent);
}
.tlc-ptitle { flex: 1; font-weight: 650; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tlc-pdone { padding: 8px 16px; border-radius: 9px; }
.tlc-wheel { display: flex; justify-content: center; }
.tlc-quick { display: grid; grid-template-columns: repeat(10, 1fr); gap: 6px; }
.tlc-qc {
  aspect-ratio: 1; width: 100%; min-width: 0; padding: 0; border-radius: 50%; cursor: pointer;
  border: 0; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 25%, transparent);
}
.tlc-qc:hover { transform: scale(1.08); filter: none; }
.tlc-hexrow { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--muted); }
.tlc-hex {
  flex: 1; min-width: 0; margin: 0; padding: 9px 11px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
  font-size: 15px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
}

/* The row's own button that opens all this. */
.openctl {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
  font: inherit; font-size: 13px;
}
.openctl:hover { border-color: var(--accent); }
.openctl:disabled { opacity: .5; cursor: default; }

/* The row's own line: the button that opens the panel, and the sync details
   beside it. */
.scene-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* ---- a group's header -----------------------------------------------------
   The name, and on one line with it everything that acts on the whole group:
   cloud sync, and local sync and audio reactive for every light in it. The
   lights themselves fold away underneath (details.grp), closed until opened. */
.ghead { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; }
/* The group's lamp (fxpreview.js) under its name, at the left of what the
   group is showing - as tall as that is. */
.gtop { display: flex; gap: 14px; align-items: stretch; }
.gmain { flex: 1 1 auto; min-width: 0; }
.gpv { flex: 0 0 36px; min-height: 62px; margin-top: 8px; }
.fxlamp { position: relative; }
.fxlamp svg { display: block; width: 100%; height: 100%; }
/* Until a lamp has drawn its first frame (fxpreview.js): a spinner. */
.fxlamp.loading::after {
  content: ''; position: absolute; left: 50%; top: 42%;
  width: 16px; height: 16px; max-width: 70%; aspect-ratio: 1; transform: translate(-50%, -50%);
  border-radius: 50%; box-sizing: border-box;
  border: 2px solid color-mix(in srgb, var(--fg) 22%, transparent); border-top-color: var(--accent);
  animation: fx-spin .8s linear infinite;
}
@keyframes fx-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }
/* A light's own lamp, in a group that is not synced: at the row's left,
   beside its name and its power and brightness both. */
.light.has-pv > .lpv { align-self: stretch; min-height: 48px; }
/* A preset's look, on request: a lamp beside its preview button. */
.fxpop {
  position: fixed; z-index: 70; width: 112px; padding: 10px 10px 8px;
  border-radius: 12px; background: var(--card); border: 1px solid var(--line);
  box-shadow: 0 12px 30px rgba(0, 0, 0, .45);
  animation: m-pop-in .18s cubic-bezier(.2, .8, .2, 1);
}
.fxpop[hidden] { display: none; }
.fxpop-lamp { width: 100%; height: 150px; }
.fxpop-name { margin-top: 6px; font-size: 12.5px; text-align: center; color: var(--muted);
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (prefers-reduced-motion: reduce) { .fxpop { animation: m-fade-in .15s ease-out; } }
@media (max-width: 480px) { .gtop { gap: 10px; } .gpv { flex-basis: 30px; min-height: 56px; } }
.ghead > h2 { margin: 0; }
.gswitches { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; }
/* The group's switches - and a light's, in its Light menu (.swbtns) - as
   toggle buttons: lit while on, a dashed edge while a group's lights disagree. The checkbox stays - hidden - so the forms, their
   confirmations and the poll's repainting work as they did. */
:is(.gswitches, .swbtns) .sw {
  position: relative; margin: 0; gap: 7px; min-height: 30px; padding: 0 12px 0 10px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--muted) 50%, transparent); background: transparent;
  color: var(--muted); font-size: 12.5px; font-weight: 600; user-select: none;
  transition: background .15s, border-color .15s, color .15s;
}
/* A status light in front: hollow while off, solid while on, half while
   the group's lights disagree - so on and off differ by more than colour. */
:is(.gswitches, .swbtns) .sw::before {
  content: ''; flex: none; width: 8px; height: 8px; border-radius: 50%;
  box-sizing: border-box; border: 1.5px solid currentColor;
}
:is(.gswitches, .swbtns) .sw.icon { padding: 0 9px 0 9px; }
:is(.gswitches, .swbtns) .sw input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none; }
:is(.gswitches, .swbtns) .sw:hover { border-color: var(--accent); color: var(--text); }
/* On: filled, as the page's buttons are. */
:is(.gswitches, .swbtns) .sw:has(input:checked) { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
:is(.gswitches, .swbtns) .sw:has(input:checked)::before { background: currentColor; }
:is(.gswitches, .swbtns) .sw:has(input:indeterminate) {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  border: 1px dashed var(--accent); color: var(--text);
}
:is(.gswitches, .swbtns) .sw:has(input:indeterminate)::before { background: linear-gradient(90deg, currentColor 50%, transparent 50%); }
:is(.gswitches, .swbtns) .sw:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
:is(.gswitches, .swbtns) .sw.locked:hover { border-color: var(--line); }
:is(.gswitches, .swbtns) form.busy .sw { opacity: .6; }
.swicon { display: grid; }
/* The hover box of an icon button (site.js): its name, and what it does. */
.tltip {
  position: fixed; z-index: 1000; max-width: 240px; padding: 7px 10px;
  display: grid; gap: 2px; pointer-events: none;
  background: var(--card-2); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .45); font-size: 12px; line-height: 1.35;
  opacity: 0; transform: translateY(2px); transition: opacity .12s, transform .12s;
}
.tltip.on { opacity: 1; transform: none; }
.tltip b { color: var(--text); font-weight: 600; }
.tltip span { color: var(--muted); }
/* The Light menu's button: a gear, no text and no arrow. */
details.pop > summary.popbtn.gear { padding: 6px 8px; }
details.pop > summary.popbtn.gear::after { content: none; }
.popbtn.gear svg { width: 17px; height: 17px; display: block; fill: none; stroke: currentColor;
                   stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.swicon svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.9;
              stroke-linecap: round; stroke-linejoin: round; }
.swicon .pip { fill: currentColor; stroke: none; }
/* Text for screen readers only. */
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* The switches wrap in their own room, so the Group menu keeps its place
   top right however long the group's name. */
.ghead > h2 { flex: none; }
.ghead > .gswitches { flex: 1 1 0; min-width: 200px; }
.gswitches form { margin: 0; }
.ghead details.group-edit { width: auto; margin: 0 0 0 auto; }

/* The group's look - power, brightness, Effects & colour - driving its lead
   light, whose change sync then carries to the rest. Only while the group
   syncs: cloud sync on (.synced), or every light on local sync (.lsync). */
.gscene { display: none; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
.group.synced .gscene, .group.lsync .gscene { display: flex; }
.gscene .ctl { flex: 1 1 260px; margin: 0; }
/* ...and while it does, the same controls on each light would only move
   together, so they are not offered twice. */
.group.synced .light .lctl, .group.lsync .light .lctl { display: none; }
/* A light's own power, brightness and Effects & colour: one line, as the
   group's (.gscene). */
.lctl { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.lctl .ctl { flex: 1 1 260px; margin: 0; }

details.grp { margin-top: 12px; }
details.grp > summary {
  cursor: pointer; list-style: none; color: var(--muted); font-size: 13px;
  padding: 4px 0; user-select: none;
}
details.grp > summary::-webkit-details-marker { display: none; }
details.grp > summary::before { content: "› "; }
details.grp[open] > summary::before { content: "⌄ "; }
details.grp > summary:hover { color: var(--text); }
details.grp > .lights { margin-top: 8px; }

/* ---- a light's row ---------------------------------------------------------
   In a synced group one line: status and name on the left, its signs
   (microphone, battery, WiFi) and its buttons on the right. In a group that
   is not synced, where every light has its own power and brightness, those
   come under the name and the buttons under them, at the right. An offline
   light keeps only Edit - the others would talk to a light that is not
   there. */
.light > .lhead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
.lhead .state { margin-left: auto; }
/* Not synced: the name, the light's own power and brightness, then its
   buttons at the right - its lamp, if it has one, at the left of all three. */
.group:not(.synced):not(.lsync) .light {
  grid-template-columns: minmax(0, 1fr); grid-template-areas: "head" "ctl"; row-gap: 8px;
}
.group:not(.synced):not(.lsync) .light.has-pv {
  grid-template-columns: 30px minmax(0, 1fr); column-gap: 12px;
  grid-template-areas: "pv head" "pv ctl";
}
/* Local sync (.lsync): one line like a synced group - but each light keeps
   its own lamp, at the left of the line. It had no column in that layout
   and was drawn as large as the row allowed. */
.group.lsync .light.has-pv {
  grid-template-columns: 26px minmax(0, 1fr); column-gap: 12px;
  grid-template-areas: "pv head";
}
.group.lsync .light.has-pv > .lpv { min-height: 40px; }
.light.offline .openctl, .light.offline .lset { display: none; }

/* Sync, Edit and a group's Group: a button that opens a small panel OVER the
   page, anchored under it. Opening one moves nothing - the old disclosures
   opened inside the row and shoved everything after them down and sideways.
   app.js keeps it to one open at a time and closes it on a click elsewhere or
   Esc; without scripting the button still opens and closes it. */
details.pop { position: relative; }
details.pop > summary.popbtn {
  list-style: none; display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 12px; border-radius: 8px; cursor: pointer; user-select: none;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
  font-size: 13px; line-height: 1.2; white-space: nowrap;
}
details.pop > summary.popbtn::-webkit-details-marker { display: none; }
details.pop > summary.popbtn::after {
  content: ""; width: 6px; height: 6px; margin-top: -3px; opacity: .7;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg); transition: transform .15s, margin .15s;
}
details.pop > summary.popbtn:hover { border-color: var(--accent); }
details.pop[open] > summary.popbtn { border-color: var(--accent); background: var(--card-2); }
details.pop[open] > summary.popbtn::after { transform: rotate(-135deg); margin-top: 3px; }
details.pop > .popbody {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
  width: max-content; min-width: 260px; max-width: min(360px, calc(100vw - 32px));
  display: grid; gap: 12px; padding: 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .5);
}
.popbody .switches { gap: 10px 18px; padding: 0; }
.popbody .swbtns { gap: 6px 8px; }
/* The Light menu's switches and audio mode, set off from its name and group. */
.lset { display: grid; gap: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
.popbody .pick > span { flex: 0 0 auto; }

/* The Edit and Group panels' forms: labelled fields, one per line. */
.editform { display: grid; gap: 10px; margin: 0; }
.editform .field { display: grid; gap: 4px; font-size: 12.5px; color: var(--muted); }
.editform .field input, .editform .field select { margin: 0; }
.editform button[type=submit] { justify-self: end; }
.editform + .editform { margin-top: 10px; }
/* A field and its save button in one line (SAVE_ICON in app.py). */
.saverow { display: flex; align-items: center; gap: 6px; }
.saverow input, .saverow select { flex: 1 1 auto; min-width: 0; width: auto; margin: 0; }
button.iconsave {
  flex: none; display: inline-grid; place-items: center; width: 38px; height: 38px; padding: 0;
  border-radius: 7px;
}
button.iconsave svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 2;
                      stroke-linecap: round; stroke-linejoin: round; }
.popfoot { margin: 0; padding-top: 10px; border-top: 1px solid var(--line); }
.popfoot button { padding: 2px 0; }

/* The group header's Group panel sits with the switches, pushed right. */
.ghead > details.pop.group-edit { margin-left: auto; }
/* The panels reuse the names of the old in-row disclosures (edit, scene), so
   the rules written for those must not reach them: full width, a "›" before
   the label, the dim summary. */
details.pop.edit, details.pop.scene, details.pop.group-edit { width: auto; margin: 0; }
details.pop > summary.popbtn::before { content: none; }
details.pop.scene > summary { opacity: 1; font-size: 13px; }

/* ---- top bar: sign out ------------------------------------------------- */
/* A real button rather than a link-styled one: leaving is a deliberate act,
   and it should look like one without shouting. Icon only on a phone. */
button.signout {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px 6px 10px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: var(--bg); color: var(--muted);
  font: inherit; font-size: 13px; font-weight: 500; line-height: 1;
}
button.signout svg {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
button.signout:hover { color: var(--text); border-color: var(--bad); filter: none; }
/* Touch screens: the first tap arms it (site.js), the second signs out. */
button.signout.armed, button.signout.armed svg { color: var(--bad); border-color: var(--bad); }
button.signout:hover svg { color: var(--bad); }
@media (max-width: 480px) {
  nav { gap: 12px; }
  button.signout { padding: 6px; }
  button.signout span { display: none; }
}

/* ---- the group menu's Presets part --------------------------------------- */
.psync { display: grid; gap: 8px; padding-top: 12px; border-top: 1px solid var(--line); }
.popbody > .psync:first-child { padding-top: 0; border-top: 0; }
.psync h3 { margin: 0; font-size: 13px; font-weight: 650; }
.psync p { margin: 0; font-size: 12.5px; line-height: 1.45; }
.psyncform { display: grid; gap: 10px; margin: 0; }
.psyncform .field { display: grid; gap: 4px; font-size: 12.5px; color: var(--muted); }
.psyncform .field select { margin: 0; }
.psyncform button[type=submit]:not(.link) { justify-self: end; }
.psyncform.busy { opacity: .6; pointer-events: none; }

/* ---- control panel: presets --------------------------------------------- */
.tlc-pwrap { position: relative; display: flex; }
.tlc-pwrap .tlc-preset { flex: 1; min-width: 0; padding-right: 42px; }
.tlc-psave {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 32px; height: 32px; padding: 0; border-radius: 8px; cursor: pointer;
  display: grid; place-items: center;
  border: 0; background: transparent; color: var(--muted);
}
.tlc-psave svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8;
                 stroke-linecap: round; stroke-linejoin: round; }
.tlc-psave:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); filter: none; }
.tlc-psave:disabled { opacity: .45; }
.tlc-tabbtn.dim { opacity: .55; }
.tlc-poff { line-height: 1.5; }

/* Numbers, renaming, saving the current look, playlists. Everything that is
   not a preset card spans the whole grid. */
.tlc-presets > .tlc-hint, .tlc-presets > .tlc-empty, .tlc-ptools, .tlc-pform { grid-column: 1 / -1; }
.tlc-presets > .tlc-hint { margin: 0; }
/* Wide enough for a name beside the two buttons: one column on a phone,
   three across the full-width card. A long name takes a second line. */
.tlc-presets { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 8px; }
.tlc-pwrap .tlc-preset {
  display: flex; align-items: center; gap: 8px; text-align: left;
  min-height: 58px; padding: 22px 76px 10px 46px;
}
/* What it plays: a small lamp (control.js drawPresetLamps, fxpreview.js), on
   the left of the card - taps go through to the preset. */
.tlc-plamp {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  width: 26px; height: 50px; pointer-events: none;
}
.tlc-plamp.none { display: grid; place-items: center; color: var(--accent); }
.tlc-plamp.none svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8;
                      stroke-linecap: round; stroke-linejoin: round; }
.tlc-pname { min-width: 0; overflow: hidden; overflow-wrap: anywhere; line-height: 1.3;
             display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.tlc-pid {
  position: absolute; left: 46px; top: 7px; pointer-events: none;
  font-size: 11px; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums;
}
.tlc-pid::before { content: "#"; }
/* The preset the light starts with: a power sign, lit on the one it does
   (control.js data-pboot) - left of Edit, where there is room for it. */
.tlc-pwrap.boots .tlc-preset { padding-right: 110px; }
.tlc-pboot {
  position: absolute; right: 74px; top: 50%; transform: translateY(-50%);
  width: 32px; height: 32px; padding: 0; border-radius: 8px; cursor: pointer;
  display: grid; place-items: center; border: 0; background: transparent;
  color: color-mix(in srgb, var(--muted) 55%, transparent);
}
.tlc-pboot svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2;
                 stroke-linecap: round; stroke-linejoin: round; }
.tlc-pboot:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); filter: none; }
.tlc-pboot.on { color: var(--accent); }
.tlc-pboot:disabled { opacity: .45; }
.tlc-pedit {
  position: absolute; right: 40px; top: 50%; transform: translateY(-50%);
  width: 32px; height: 32px; padding: 0; border-radius: 8px; cursor: pointer;
  display: grid; place-items: center;
  border: 0; background: transparent; color: var(--muted);
}
.tlc-pedit svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.8;
                 stroke-linecap: round; stroke-linejoin: round; }
.tlc-pedit:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); filter: none; }

.tlc-ptools { display: flex; flex-wrap: wrap; gap: 8px; }
.tlc-pbtn {
  padding: 7px 12px; border-radius: 8px; font-size: 13.5px; font-weight: 600;
  background: transparent; color: var(--accent); border: 1px dashed color-mix(in srgb, var(--accent) 60%, transparent);
}
.tlc-pbtn:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); filter: none; }
.tlc-pbtn:disabled { opacity: .45; cursor: not-allowed; }

.tlc-pform {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 12px;
  padding: 12px 14px; border-radius: 10px;
  background: var(--card-2); border: 1px solid var(--line);
}
.tlc-pform h4 { flex: 1 1 100%; margin: 0; font-size: 14px; }
.tlc-pform label { display: grid; gap: 4px; margin: 0; font-size: 12.5px; flex: 1 1 180px; }
.tlc-pform input:not([type=checkbox]), .tlc-pform select {
  margin: 0; padding: 7px 9px; font-size: 13.5px; width: 100%;
  background: var(--bg); border: 1px solid var(--line); border-radius: 7px; color: var(--text); font: inherit;
}
.tlc-pform label.tlc-pnum { flex: 0 0 96px; }
.tlc-pform .tlc-hint { flex: 1 1 100%; margin: 0; font-size: 12.5px; }
.tlc-pacts { display: flex; align-items: center; gap: 12px; flex: 1 1 100%; }
.tlc-pacts button { padding: 7px 14px; }
.tlc-prow2, .tlc-prow3 { display: flex; flex-wrap: wrap; gap: 10px 12px; flex: 1 1 100%; }
.tlc-prow3 label { flex: 1 1 110px; }
.tlc-plist { display: grid; gap: 6px; flex: 1 1 100%; }
.tlc-pentry { display: flex; align-items: center; gap: 6px; }
.tlc-pentry select { flex: 1 1 auto; min-width: 0; }
.tlc-pidx { flex: none; width: 18px; font-size: 12px; color: var(--muted); text-align: right; }
.tlc-pdur { display: flex !important; align-items: center; gap: 4px; flex: 0 0 auto !important; color: var(--muted); }
.tlc-pentry .tlc-pdur input { width: 76px; }
.tlc-prow3 .tlc-pdur input { width: 100%; }
.tlc-pmini {
  flex: none; width: 30px; height: 30px; padding: 0; border-radius: 7px;
  background: transparent; color: var(--muted); border: 1px solid var(--line); font-weight: 600;
}
.tlc-pmini:hover { color: var(--text); border-color: var(--accent); filter: none; }
.tlc-pmini:disabled { opacity: .35; cursor: default; }
.tlc-pform .tlc-rpsm { display: flex; align-items: center; gap: 8px; flex: 1 1 100%; color: var(--text); font-size: 13.5px; cursor: pointer; }

/* ---- Saved presets page ------------------------------------------------- */
/* ---- Saved presets: a card each, its lamp beside it ----------------------- */
.ptop { display: flex; align-items: center; justify-content: space-between; gap: 10px 16px;
        flex-wrap: wrap; margin: 0 0 14px; }
.ptop h1 { margin: 0; }
/* The tabs, and on the Saved tab New preset at the other end. */
.ptabrow { display: flex; align-items: center; justify-content: space-between; gap: 10px;
           flex-wrap: wrap; margin: 0 0 14px; }
.ptabrow .ptabs { margin: 0; }
.pnew { padding: 8px 14px; font-size: 13.5px; }
.pgrid:empty { display: none; }
/* Saved | Community - the Presets page's two tabs. */
.ptabs { display: inline-flex; gap: 4px; padding: 4px; margin: 0 0 14px; border-radius: 999px;
         background: var(--glass); border: 1px solid var(--line); }
.ptabs a { padding: 6px 16px; border-radius: 999px; font-size: 14px; color: var(--muted);
           text-decoration: none; }
.ptabs a:hover { color: var(--text); }
.ptabs a[aria-current="page"] { background: var(--accent); color: var(--on-accent); font-weight: 600; }
.pvlive {
  display: inline-flex; align-items: center; gap: 10px; cursor: pointer;
  padding: 6px 8px 6px 14px; border-radius: 999px; background: var(--glass);
  border: 1px solid var(--line); font-size: 13.5px;
}
.pvlive .toggle { order: 2; }
.pgrid { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 12px;
         grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
.pcard {
  display: grid; grid-template-columns: 52px 1fr; gap: 14px; align-items: stretch;
  padding: 14px 16px 14px 12px; border-radius: var(--radius);
  background: var(--glass); border: 1px solid var(--line);
}
.plamp { width: 52px; height: 104px; align-self: center; }
.plamp.none { display: grid; place-items: center; border-radius: 12px; color: var(--muted);
              background: color-mix(in srgb, var(--fg) 5%, transparent); }
.plamp.none svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.8;
                  stroke-linecap: round; stroke-linejoin: round; }
.pbody { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.phead { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.pname { font-weight: 650; font-size: 15.5px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pplays { margin: 0; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pmeta { margin: 0; font-size: 12px; color: var(--muted); }
.pacts { margin-top: auto; padding-top: 10px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.pacts .copyto { padding: 7px 12px; font-size: 13.5px; }
.pacts .ppreview[aria-pressed="true"] { color: var(--accent); }
body.pv-live .pacts .ppreview { display: none; }
.phead details.pmore { margin-left: auto; align-self: center; }
.phead details.pmore > summary.popbtn { padding: 3px 9px; font-size: 12.5px; }
.phead .pdel { margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--line); }
@media (max-width: 480px) { .pgrid { grid-template-columns: 1fr; } }

/* ---- Community presets: the Saved presets card, plus who shared it, what
   they said about it, its stars and any try-out running ------------------- */
.pcard[hidden] { display: none; }
.pbadge { flex: none; align-self: center; padding: 1px 8px; border-radius: 999px; font-size: 11.5px;
          color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); }
.phead .cedit { margin-left: auto; align-self: center; padding: 0; font-size: 13px; }
.pby { margin: 0; font-size: 12.5px; color: var(--muted); }
.pby strong { color: var(--text); font-weight: 600; }
.pdesc { margin: 2px 0 0; font-size: 13.5px; line-height: 1.4; overflow-wrap: anywhere; }
.pacts .ctry { margin-left: auto; }
/* Design and Share... at the top of a card's More, over rename and delete. */
.pmacts { display: flex; gap: 16px; margin: 0 0 12px; padding-bottom: 10px;
          border-bottom: 1px solid var(--line); }
.pmacts button.link { padding: 2px 0; }
.pacts .csave { padding: 7px 12px; font-size: 13.5px; }
.cstars { display: flex; align-items: center; justify-content: space-between; gap: 6px 12px;
          flex-wrap: wrap; margin-top: 4px; }
.savg { display: inline-flex; align-items: baseline; gap: 6px; font-size: 13px; }
/* The average as five stars filled to it (--p, 0-100%). */
.stars { font-size: 15px; letter-spacing: 1px; line-height: 1;
         background: linear-gradient(90deg, var(--warn) var(--p, 0%),
                     color-mix(in srgb, var(--muted) 45%, transparent) var(--p, 0%));
         -webkit-background-clip: text; background-clip: text; color: transparent; }
.snum { font-weight: 600; font-variant-numeric: tabular-nums; }
.scount { color: var(--muted); font-size: 12px; }
/* One's own stars: filled up to the one pointed at while pointing. */
.srate { display: inline-flex; }
.srate .star { padding: 2px 3px; background: none; border-radius: 4px; font-size: 18px; line-height: 1;
               font-weight: 400; color: color-mix(in srgb, var(--muted) 55%, transparent); }
.srate .star.on { color: var(--warn); }
.srate:hover .star { color: var(--warn); }
.srate .star:hover ~ .star { color: color-mix(in srgb, var(--muted) 55%, transparent); }
.srate .star:focus-visible { outline: 2px solid var(--accent); outline-offset: 0; }
.ptrying:empty { display: none; }
.ptrying { display: grid; gap: 6px; margin-top: 8px; }
.tryrow { display: flex; align-items: center; justify-content: space-between; gap: 10px;
          padding: 6px 6px 6px 12px; border-radius: 8px; font-size: 13px;
          background: color-mix(in srgb, var(--accent) 12%, transparent);
          border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); }
.trysec { font-variant-numeric: tabular-nums; color: var(--accent); }
.tryrow .trystop { padding: 4px 12px; font-size: 13px; }
.ctools { display: flex; gap: 10px; flex-wrap: wrap; margin: 14px 0 0; }
.ctools input[type=search] {
  flex: 1 1 220px; min-width: 0; margin: 0; padding: 9px 11px; background: var(--card-2);
  border: 1px solid var(--line); border-radius: 7px; color: var(--text); font: inherit;
}
.ctools select { width: auto; flex: 0 0 auto; margin: 0; }
#cmNone { margin-top: 14px; }
#cmNone.hide, .copydlg .hide { display: none; }
.copybody textarea { margin: 0; resize: vertical; min-height: 64px; }
#shMsg:empty { display: none; }
/* A saved preset changed since it was shared. */
/* New playlist: a row per preset, like the lamp panel's playlist editor. */
.pnews { display: flex; gap: 8px; flex-wrap: wrap; }
.pldlg { width: min(560px, calc(100vw - 32px)); }
.plentries { display: grid; gap: 6px; }
.plentry { display: flex; align-items: center; gap: 6px; }
.plentry select { flex: 1 1 auto; min-width: 0; margin: 0; padding: 7px 8px; }
.plidx { flex: none; width: 18px; font-size: 12px; color: var(--muted); text-align: right; }
.pldur { display: flex; align-items: center; gap: 4px; flex: none; margin: 0; font-size: 13px; color: var(--muted); }
.pldlg input[type=number] {
  margin: 0; width: 100%; box-sizing: border-box; padding: 7px 8px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--card-2); color: var(--text);
  font: inherit; font-size: 14px;
}
.pldur input[type=number] { width: 76px; }
.plmini { flex: none; width: 28px; height: 30px; padding: 0; border-radius: 7px; font-size: 14px;
          background: var(--card-2); color: var(--muted); border: 1px solid var(--line); }
.plmini:hover { color: var(--text); border-color: var(--accent); filter: none; }
.plmini:disabled { opacity: .35; cursor: default; }
.plopts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.plopts select { margin: 0; padding: 7px 8px; }
.plshuf { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
.plshuf input { width: 17px; height: 17px; margin: 0; accent-color: var(--accent); }
@media (max-width: 480px) { .plopts { grid-template-columns: 1fr 1fr; } }
.copybody .field input { margin: 0; }

.copydlg {
  width: min(520px, calc(100vw - 32px)); padding: 0;
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--card); color: var(--text);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
}
/* Dimmed, not blurred: the lamps behind keep moving (see .tlc). */
.copydlg::backdrop { background: rgba(0, 0, 0, .65); }
.copybody { display: grid; gap: 14px; padding: 18px; }
.copybody h2 { margin: 0; font-size: 16px; }
.copybody .field { display: grid; gap: 4px; font-size: 12.5px; color: var(--muted); }
.copybody .field select { margin: 0; }
.cprows { display: grid; gap: 6px; }
.cprows:empty { display: none; }
.cphead, .cprow { display: grid; grid-template-columns: 1fr 76px minmax(96px, 38%); gap: 10px; align-items: center; }
.cphead { font-size: 11.5px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.cpn { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
.cpn em { font-style: normal; font-size: 11.5px; color: var(--muted); margin-left: 4px; }
.cprow input {
  margin: 0; width: 100%; box-sizing: border-box; padding: 7px 8px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg); color: var(--text); font: inherit; font-size: 14px;
}
.cprow input:focus { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 1px; }
/* Wraps rather than truncating: the name of the preset about to be replaced
   is the one thing on this row that must be read in full. */
.cpst { font-size: 12.5px; line-height: 1.3; color: var(--muted); overflow-wrap: anywhere; }
.cpst.free { color: var(--ok); }
.cpst.warn { color: var(--warn); }
.cpst.err { color: var(--bad); }
.cptools { display: flex; justify-content: flex-end; }
.cphint { margin: 0; font-size: 12.5px; color: var(--muted); }
/* A preset going to another kind of light (presets.js, convBlock). */
.cpconv { display: grid; gap: 6px; padding: 10px 12px; margin-bottom: 4px; border-radius: 10px;
          border-left: 3px solid var(--warn); background: color-mix(in srgb, var(--warn) 9%, transparent); }
.cpconv .cpwarn { margin: 0; font-size: 13px; }
.cpconv .cprow.cpconvrow { grid-template-columns: 1fr minmax(120px, 45%); }
.cpconv .cpconvrow .cpn { white-space: normal; overflow-wrap: anywhere; }
@media (max-width: 480px) { .cpconv .cprow.cpconvrow { grid-template-columns: 1fr; gap: 4px; } }
.cpkind { display: inline-block; padding: 0 6px; border-radius: 6px; font-size: 11.5px;
          background: color-mix(in srgb, var(--fg) 8%, transparent); color: var(--muted); }
p.cphint.cpwarn { color: var(--warn); }
/* The kind of light a saved preset was made on (app.py, _saved_card). */
.ptype { display: inline-block; padding: 0 6px; margin-right: 4px; border-radius: 6px; font-size: 11.5px;
         background: color-mix(in srgb, var(--fg) 8%, transparent); }
.cpmsg { margin: 0; min-height: 1.2em; font-size: 13px; color: var(--muted); }
.cpmsg.bad { color: var(--bad); }
.dlgacts { display: flex; justify-content: flex-end; gap: 12px; }

/* Status messages (site.js): a stack at the bottom, newest last. */
.toasts {
  position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom, 0px)); z-index: 100;
  transform: translateX(-50%); width: max-content; max-width: min(520px, calc(100vw - 32px));
  display: flex; flex-direction: column; align-items: center; gap: 8px; pointer-events: none;
}
.ptoast {
  padding: 10px 16px; border-radius: 10px; font-size: 14px; line-height: 1.4;
  background: var(--card-2); color: var(--text); border: 1px solid var(--ok);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .5);
  opacity: 0; transform: translateY(8px); transition: opacity .4s, transform .4s;
}
.ptoast.in { opacity: 1; transform: none; }
.ptoast.bad { border-color: var(--bad); }

/* The page heading, with the order of the lights beside it. */
.pagehead { display: flex; align-items: center; justify-content: space-between; gap: 10px 16px; flex-wrap: wrap; margin: 0 0 18px; }
.pagehead h1 { margin: 0; }
.lsort { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); margin: 0; }
.lsort select { margin: 0; width: auto; padding: 6px 10px; font-size: 13px; }
.lview { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 18px; }
.lonly { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; color: var(--muted); margin: 0; cursor: pointer; }
.lonly input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
li.light.offhide { display: none !important; }
.offnote { margin: 4px 0 0; font-size: 13px; }

/* ---- shared sync ---------------------------------------------------------
   The Shared Sync page, and the line on a dashboard group saying it is shared
   or following a share. */
.shareline {
  display: flex; align-items: center; gap: 10px;
  margin: 10px 0 0; padding: 7px 12px 7px 10px; border-radius: 10px;
  font-size: 13px; line-height: 1.4; color: var(--muted);
  background: color-mix(in srgb, var(--fg) 4%, transparent);
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
}
.shareline.follow { border-left-color: var(--warn); }
/* A part of a light that does not go with its group (a row each). */
.shareline.part { margin-top: 6px; }
.shareline.part.own { border-left-color: var(--muted); }
.shareline.part.own .shicon { color: var(--muted); }
/* Shared Sync's Parts of lights: one form per light, a select per part. */
form.parts { margin: 0 0 14px; padding: 12px 14px; border: 1px solid var(--line); border-radius: 12px; }
form.parts h3 { margin: 0 0 8px; font-size: 15px; }
form.parts .field.sparts { display: flex; align-items: center; gap: 10px; margin: 6px 0; }
form.parts .field.sparts span { min-width: 90px; }
form.parts .field.sparts select { flex: 1; min-width: 0; }
.shareline .shicon { flex: none; display: grid; color: var(--accent); }
.shareline.follow .shicon { color: var(--warn); }
.shareline .shicon svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.9;
                         stroke-linecap: round; stroke-linejoin: round; }
.shareline .shtext { flex: 1 1 auto; min-width: 0; }
.shareline .shtext b { color: var(--text); font-weight: 600; }
.shareline .shacts { flex: none; display: inline-flex; align-items: center; gap: 14px; }
.shareline a { color: var(--accent); text-decoration: none; }
.shareline form.inline { display: inline-flex; margin: 0; }
.shareline button.link { padding: 0; font-size: 13px; border: 0; background: none; }
.shareline button.link:hover { text-decoration: underline; }
@media (max-width: 560px) {
  .shareline { flex-wrap: wrap; }
  .shareline .shacts { margin-left: 26px; }
}
.sgown { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; margin-right: 10px; }
.sgown form.inline { display: inline-flex; margin: 0; }
.shareline a:hover { text-decoration: underline; }
.sbadge {
  display: inline-block; flex: none; padding: 2px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .02em; white-space: nowrap;
  border: 1px solid var(--line); background: var(--card-2); color: var(--text);
}
.sbadge.public { border-color: color-mix(in srgb, var(--accent) 60%, var(--line)); color: var(--accent); }
.sbadge.named { border-color: color-mix(in srgb, var(--ok) 55%, var(--line)); color: var(--ok); }
.sbadge.follow { border-color: color-mix(in srgb, var(--warn) 55%, var(--line)); color: var(--warn); }

.lead { max-width: 70ch; margin: -6px 0 22px; }
.small { font-size: 13px; }
.share .field, .snew .field, .sfollow .field {
  display: grid; gap: 4px; flex: 1 1 180px; margin: 0; font-size: 12.5px; color: var(--muted);
}
.share .field select, .snew .field select, .sfollow .field select { margin: 0; width: 100%; }
.share .row, .snew, .sfollow { align-items: end; }
.shead { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px 16px; }
.stitle { min-width: 0; }
/* A share's lamp beside its name (static/sync.js). */
.shmain { display: flex; align-items: center; gap: 14px; min-width: 0; }
.shpv { flex: 0 0 40px; height: 80px; }
.spreview { margin-left: 10px; font-size: 13px; }
.spreview[aria-pressed="true"] { color: var(--accent); }
body.pv-live .spreview { display: none; }
.stitle h3 { margin: 0 0 2px; font-size: 17px; overflow-wrap: anywhere; }
.sowner { margin: 0; font-size: 13px; color: var(--muted); }
.snow { margin: 2px 0 0; font-size: 14px; }
.snow .muted { font-size: 12.5px; margin-left: 6px; }
/* Shared Sync: a card per part, its shares one under the other in it. */
.shares > h2 { margin: 0 0 6px; font-size: 18px; }
.shares > p.small { margin: 0 0 14px; }
.share { padding: 16px 0 4px; border-top: 1px solid var(--line); margin-top: 14px; }
.shares > h2 + .share, .shares > h2 + p + .share { margin-top: 8px; }
.share h4 {
  margin: 18px 0 8px; font-size: 11.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
}
.snew { display: grid; gap: 14px; margin: 0; }
.snew .field { max-width: 380px; }
.snew button { justify-self: start; }
.svchoice { border: 0; margin: 0; padding: 0; min-width: 0; }
.svchoice legend { padding: 0; margin: 0 0 6px; font-size: 12.5px; color: var(--muted); }
.vopt {
  display: flex; align-items: flex-start; gap: 10px; margin: 0 0 8px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 8px; cursor: pointer; color: var(--text);
}
.vopt:last-child { margin-bottom: 0; }
.vopt:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.vopt:has(input:checked) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
.vopt input { flex: none; margin: 3px 0 0; accent-color: var(--accent); }
.vopt b { display: block; font-size: 14px; font-weight: 600; }
.vopt small { display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted); }
.subs, .sfollowing, .nchips { list-style: none; margin: 0; padding: 0; }
.subs { display: grid; gap: 6px; }
.sub {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 12px;
  padding: 9px 12px; border-radius: 8px; background: var(--card-2);
}
.suser { font-weight: 600; }
.sgroups { flex: 1 1 200px; min-width: 0; font-size: 13px; color: var(--muted); }
.sub form.inline, .sfollowing form.inline { display: inline-flex; align-items: center; gap: 8px; margin: 0; }
.sacc { font-size: 12.5px; color: var(--muted); }
.sacc.w { color: var(--ok); }
p.sacc { margin: 8px 0 0; }
.sfollowing { display: grid; gap: 4px; margin-top: 12px; }
.sfollowing li { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 10px; }
.sgname { font-weight: 600; }
.nchips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.nchip {
  display: inline-flex; align-items: center; gap: 2px; padding: 2px 4px 2px 11px;
  border-radius: 999px; background: var(--card-2); border: 1px solid var(--line); font-size: 13px;
}
.nchip form { margin: 0; }
.nchip .x {
  padding: 0 6px; background: none; color: var(--muted); font-size: 16px; line-height: 1; font-weight: 400;
}
.nchip .x:hover { color: var(--bad); filter: none; }
.sstop { margin-top: 16px; display: flex; justify-content: flex-end; }
@media (max-width: 560px) {
  .shead { flex-direction: column; }
}

/* ---- Random Cycle, the same on every light (control panel) -------------- */
.tlc-rp {
  margin: 0 0 12px; padding: 12px 14px; border-radius: 10px;
  background: var(--card-2); border: 1px solid var(--line);
}
.tlc-rp.hide { display: none; }
.tlc-rp label { margin: 0; color: var(--text); }
.tlc-rpon { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.tlc-rpon input { width: 18px; height: 18px; margin: 2px 0 0; flex: none; accent-color: var(--accent); }
.tlc-rpon b { display: block; font-size: 14px; font-weight: 600; }
.tlc-rpon small { display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted); }
.tlc-rpopts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px 12px;
  margin-top: 12px;
}
.tlc-rpopts.off { opacity: .55; }
.tlc-rpopts label { display: grid; gap: 4px; font-size: 12.5px; color: var(--muted); }
.tlc-rpopts select { margin: 0; padding: 7px 9px; font-size: 13.5px; }
.tlc-rpopts .tlc-rpsm { display: flex; align-items: center; gap: 8px; grid-column: 1 / -1; color: var(--text); font-size: 13.5px; cursor: pointer; }
.tlc-rpsm input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
.tlc-rp .tlc-hint { margin: 10px 0 0; }

/* ---- animated background (bgfx.js) --------------------------------------- */
/* A WebGL canvas the page's size, behind everything; bgfx.js draws it a few
   times a second. Nothing here asks for work per frame. */
canvas.bgfx {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  display: block;
  pointer-events: none;
}

/* ---- header: the Support badge -------------------------------------------- */
nav a.navsup { display: inline-flex; align-items: center; }
.nbadge {
  min-width: 17px; height: 17px; padding: 0 5px; margin-left: 5px;
  border-radius: 9px; background: var(--accent); color: var(--on-accent);
  font-size: 11px; font-weight: 700; line-height: 17px; text-align: center;
}
.nbadge[hidden] { display: none; }
/* A phone: less padding and spacing in the header (the signed-out one's two
   links; the signed-in menu folds away - see "the menu" below). */
@media (max-width: 480px) {
  header { padding: 12px; gap: 10px; }
  nav { gap: 8px; flex-wrap: wrap; justify-content: flex-end; row-gap: 6px; }
  nav a { font-size: 13px; }
}
@media (max-width: 400px) {
  nav { gap: 7px; }
  nav a { font-size: 12.5px; }
}

/* ---- the menu (page() in app.py) ----------------------------------------------
   Three sections: your lights, help, your account. In one row with a line
   between the sections; below 760 px folded behind a burger - a checkbox
   and its label, so it opens without script - into a panel with the
   sections' names. */
.navopen { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none; }
.burger, .navhd { display: none; }
.navsheet, .navsec { display: flex; align-items: center; gap: 16px; }
.navsec + .navsec { padding-left: 16px; border-left: 1px solid var(--line); }
@media (max-width: 760px) {
  .burger {
    display: inline-flex; position: relative; align-items: center; justify-content: center;
    width: 40px; height: 34px; border: 1px solid var(--line); border-radius: 8px;
    color: var(--muted); cursor: pointer; margin: 0;
  }
  .burger svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
  .navopen:focus-visible + .burger { outline: 2px solid var(--accent); outline-offset: 2px; }
  .navopen:checked + .burger { color: var(--text); border-color: var(--accent); }
  .navdot { position: absolute; top: 5px; right: 5px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
  .navdot[hidden] { display: none; }
  .navsheet {
    display: none; position: absolute; top: calc(100% + 6px); right: 12px; z-index: 60;
    width: min(280px, calc(100vw - 24px)); flex-direction: column; align-items: stretch; gap: 0;
    padding: 6px; border: 1px solid var(--line); border-radius: 12px;
    background: var(--glass-bar);
    box-shadow: 0 16px 40px rgba(0, 0, 0, .5);
  }
  .navopen:checked ~ .navsheet { display: flex; }
  .navsec { flex-direction: column; align-items: stretch; gap: 0; padding: 6px 0; }
  .navsec + .navsec { padding-left: 0; border-left: 0; border-top: 1px solid var(--line); }
  .navhd { display: block; padding: 4px 10px 2px; font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
  .mainnav .navsec a { padding: 13px 10px; border-radius: 8px; font-size: 15px; color: var(--text); }
  .mainnav .navsec a:hover { background: rgba(255, 255, 255, .06); }
  :root[data-theme="light"] .mainnav .navsec a:hover { background: rgba(0, 0, 0, .05); }
  .mainnav .navsec form { margin: 6px 10px 4px; }
  .mainnav button.signout { width: 100%; justify-content: center; padding: 13px 12px; }
  .mainnav button.signout span { display: inline; }
}

/* ---- the user guide (/guide, docs.py) ---------------------------------------- */
.docs { line-height: 1.6; }
.docs h1 { margin-bottom: 10px; }
.docs h2 { margin: 34px 0 10px; font-size: 19px; padding-top: 12px; border-top: 1px solid var(--line); }
.docs h3 { margin: 24px 0 8px; font-size: 16px; }
.docs h4 { margin: 18px 0 6px; font-size: 15px; color: var(--muted); }
.docs > h2:first-of-type { margin-top: 26px; }
.docs p, .docs ul, .docs ol { margin: 0 0 12px; }
/* A picture in the guide (docs.py): as wide as the text, no wider than itself. */
.docs-img { margin: 0 0 16px; }
.docs-img img { display: block; width: 100%; max-width: 640px; height: auto; border-radius: 10px; }
.docs ul, .docs ol { padding-left: 22px; }
.docs li { margin: 3px 0; }
.docs li > ul, .docs li > ol { margin: 4px 0 0; }
.docs code { padding: 1px 5px; border-radius: 5px; background: var(--card-2); font-size: 13.5px; }
.docs hr { border: 0; border-top: 1px solid var(--line); margin: 24px 0; }
.docs [id] { scroll-margin-top: 84px; }   /* clear of the sticky header */
.docs-note { margin: 14px 0; padding: 10px 14px; border-left: 3px solid var(--accent);
             background: rgba(53, 193, 232, 0.08); border-radius: 0 8px 8px 0; }
.docs-note > :last-child { margin-bottom: 0; }
.docs-warn { border-left-color: var(--warn); background: rgba(217, 164, 65, 0.12); }
.docs-table { overflow-x: auto; margin: 0 0 14px; }
.docs table { border-collapse: collapse; width: 100%; font-size: 14px; }
.docs th, .docs td { text-align: left; vertical-align: top; padding: 7px 10px; border-bottom: 1px solid var(--line); }
.docs th { color: var(--muted); font-weight: 600; }
.docs-rel { padding: 10px 0; border-top: 1px solid var(--line); }
.docs-rel h4 { margin: 0 0 4px; color: var(--text); font-size: 15px; }
.docs-rel h4 span { margin-left: 8px; font-weight: 400; font-size: 13px; color: var(--muted); }
.docs-rel p { margin: 0 0 4px; }

/* The guide's pages: its menu beside the page, built from the docs/ folder
   (docs.py), sticking below the header; on a narrow screen folded into
   Contents above the page. Previous and next under it. */
main.wide:has(.docs-layout) { max-width: 1080px; }
.docs-layout { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 18px; align-items: start; }
.docs-layout .card { margin-bottom: 0; }
.docs-menu { position: sticky; top: 78px; max-height: calc(100vh - 96px); overflow-y: auto; padding: 12px 10px; }
.docs-tree, .docs-tree ul { list-style: none; margin: 0; padding: 0; }
.docs-tree { font-size: 14px; }
.docs-tree ul { margin: 2px 0 8px 10px; padding-left: 8px; border-left: 1px solid var(--line); }
.docs-tree a, .docs-tree span { display: block; padding: 6px 9px; border-radius: 7px; line-height: 1.35; }
.docs-tree a { color: var(--muted); text-decoration: none; }
.docs-tree a:hover { color: var(--text); background: rgba(127, 127, 127, .14); }
.docs-tree > li > a, .docs-tree > li > span { color: var(--text); font-weight: 600; }
.docs-tree a.on { color: var(--text); background: color-mix(in srgb, var(--accent) 20%, transparent); }
.docs-menu-m { display: none; }
.docs-pager { display: flex; gap: 12px; margin-top: 30px; padding-top: 14px; border-top: 1px solid var(--line); }
.docs-pager a { display: flex; flex-direction: column; gap: 2px; max-width: 48%; text-decoration: none; }
.docs-pager a span { font-size: 12px; color: var(--muted); }
.docs-pager .next { margin-left: auto; text-align: right; }
@media (max-width: 820px) {
  .docs-layout { grid-template-columns: minmax(0, 1fr); }
  .docs-menu { display: none; }
  .docs-menu-m { display: block; padding: 12px 16px; margin-bottom: 14px !important; }
  .docs-menu-m summary { cursor: pointer; font-weight: 600; }
  .docs-menu-m .docs-tree { margin-top: 10px; }
}

/* ---- forms: text areas and switches ---------------------------------------- */
textarea {
  display: block;
  width: 100%;
  min-height: 96px;
  margin-top: 6px;
  padding: 9px 11px;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--text);
  font: inherit;
  resize: vertical;
}
textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
label.check { display: flex; align-items: center; gap: 9px; color: var(--text); cursor: pointer; }

/* ---- Account settings ---------------------------------------------------------
   Settings as rows - what it is and a line about it on the left, its control
   on the right - grouped into cards; a sub-part of a card under its own
   heading. */
.acct > h2 { margin: 0 0 4px; font-size: 18px; }
.setrow {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  margin: 0; padding: 14px 0; border-bottom: 1px solid var(--line);
  color: var(--text); font-size: 15px;
}
label.setrow { cursor: pointer; }
.settext { min-width: 0; }
.settext b { display: block; font-weight: 600; }
.settext small { display: block; margin-top: 2px; font-size: 13px; color: var(--muted); line-height: 1.45; }
.setval { font-weight: 600; overflow-wrap: anywhere; text-align: right; }
.setnote { margin: 10px 0 0; font-size: 13px; color: var(--muted); }
.setblock { padding: 16px 0 4px; }
.setblock + .setblock { border-top: 1px solid var(--line); margin-top: 12px; }
.setblock h3 { margin: 0 0 12px; font-size: 15px; }
.setblock form { margin: 0; }
.pwgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
.pwgrid .pwwide { grid-column: 1 / -1; }
.pwgrid button { grid-column: 1 / -1; justify-self: start; }
@media (max-width: 560px) { .pwgrid { grid-template-columns: 1fr; } }
.acctdel { margin-top: 12px; }
.acctdel > summary {
  display: inline-block; cursor: pointer; list-style: none; color: var(--bad);
  padding: 8px 14px; border: 1px solid var(--bad); border-radius: 8px; font-size: 14px;
}
.acctdel > summary::-webkit-details-marker { display: none; }
.acctdel[open] > summary { margin-bottom: 14px; }

/* An on/off switch drawn from a checkbox - still a checkbox for everything
   that reads or submits it. */
input.toggle {
  -webkit-appearance: none; appearance: none; flex: none; position: relative;
  width: 44px; height: 26px; margin: 0; border-radius: 999px; cursor: pointer;
  background: var(--line); transition: background .2s;
}
input.toggle::after {
  content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
  transition: transform .2s;
}
input.toggle:checked { background: var(--accent); }
input.toggle:checked::after { transform: translateX(18px); }
input.toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
label.check input { margin: 0; width: 17px; height: 17px; accent-color: var(--accent); }

/* ---- support tickets (support.js) ----------------------------------------- */
.tlist .trow {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 0; border-top: 1px solid var(--line);
}
.tlist .trow:first-child { border-top: 0; padding-top: 2px; }
.trow .tlink { flex: 1 1 auto; min-width: 0; display: block; color: var(--text); text-decoration: none; }
.trow .tlink:hover .tsubj { color: var(--accent); }
.tsubj { display: block; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tmeta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 4px 0 0; font-size: 12.5px; color: var(--muted); }
.tchip {
  display: inline-block; padding: 1px 8px; border-radius: 9px;
  border: 1px solid var(--line); font-size: 12px; line-height: 1.5; color: var(--muted);
}
.tchip.st-new { border-color: var(--accent); color: var(--accent); }
.tchip.st-open { border-color: var(--ok); color: var(--ok); }
.tchip.st-progress { border-color: var(--warn); color: var(--warn); }
.tunread {
  flex: none; padding: 1px 9px; border-radius: 9px;
  background: var(--accent); color: var(--on-accent); font-size: 12px; font-weight: 700;
}
.trow .tremove { flex: none; }
.back { margin: 0 0 12px; font-size: 14px; }
.back a { display: inline-block; padding: 6px 12px; border-radius: 8px; background: var(--glass); }
/* The conversation is one box, like the rest of the portal's content, so
   none of it sits on the animated background: the subject and its details,
   the messages, and the reply under a rule. */
.thread {
  background: var(--glass); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px;
}
.thread h1 { margin: 0 0 6px; overflow-wrap: anywhere; text-shadow: none; }
.thread .tclosed { margin: 14px 0 0; }
.tmsgs { display: flex; flex-direction: column; gap: 10px; margin: 18px 0; }
.tmsg {
  max-width: 88%; padding: 11px 14px;
  background: var(--glass); border: 1px solid var(--line); border-radius: var(--radius);
}
.tmsg.from-user { align-self: flex-end; border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
.tmsg.from-support { align-self: flex-start; border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.tmhead { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin-bottom: 4px; font-size: 12.5px; color: var(--muted); }
.tmhead strong { color: var(--text); font-size: 13px; }
.tmhead .tseen { color: var(--ok); }
.tmbody { white-space: pre-wrap; overflow-wrap: anywhere; }
.tmsg .tedit { margin-top: 4px; font-size: 13px; }
.tmsg .row { margin-top: 8px; }
.tmsg .notice { margin: 8px 0 0; }
.treply { padding: 14px 0 0; border-top: 1px solid var(--line); }
.treply label { margin-bottom: 10px; }
@media (max-width: 560px) {
  .tmsg { max-width: 100%; }
}

/* ---- the lights' own palettes ---------------------------------------------
   A palette's gradient on its card (Palettes tab, Community), a small one
   beside a name (the copy dialogs), and in a light's palette list the
   section they are listed under, each with its cloud button (control.js). */
.palbar { height: 14px; margin: 5px 0 3px; border-radius: 6px;
          box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); }
.cpsw { display: inline-block; width: 30px; height: 12px; margin-right: 6px; border-radius: 3px;
        vertical-align: -1px; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); }
.cpst .cpsw { margin: 0 0 0 2px; }
.cphead.cppal, .cprow.cppal { grid-template-columns: 1fr 112px minmax(96px, 38%); }
.cprow select { margin: 0; padding: 5px 6px; font-size: 13px; }
.tlc-lsec { margin: 10px 0 2px; padding: 0 10px; font-size: 11.5px; letter-spacing: .05em;
            text-transform: uppercase; color: var(--muted); }
.tlc-cwrap .tlc-item { padding-right: 42px; }

/* The Community tab's Presets and Palettes: tabs joined to the panel under
   them, which holds the search and then the cards (app.py, community_panel).

   Tab and panel are glass over the moving background, so neither can hide
   the other's border by overlapping it - the overlap would show as a darker
   line. They touch instead, and the panel's border (::before, not a border
   of its own) is cut away under the open tab with clip-path, between the
   tab's side borders: the two read as one shape. Tabs of one width, so the
   cut knows where the open one is. */
.cbox { --tw: 118px; --tg: 4px; margin-top: 4px; }
.ctabs { display: flex; gap: var(--tg); }
.ctabs a {
  box-sizing: border-box; flex: 0 0 var(--tw); padding: 9px 0; text-align: center;
  font-size: 14px; color: var(--muted); text-decoration: none;
  border: 1px solid transparent; border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0;
}
.ctabs a:hover { color: var(--text); background: color-mix(in srgb, var(--glass) 45%, transparent); }
.ctabs a[aria-current="page"] { background: var(--glass); border-color: var(--line); color: var(--text);
                                font-weight: 600; }
.cpanel {
  --cl: 1px; --cr: calc(var(--tw) - 1px);
  position: relative; padding: 16px; background: var(--glass);
  border-radius: 0 var(--radius) var(--radius) var(--radius);
}
.cpanel::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  border: 1px solid var(--line); border-radius: inherit;
  clip-path: polygon(0 0, var(--cl) 0, var(--cl) 1px, var(--cr) 1px, var(--cr) 0,
                     100% 0, 100% 100%, 0 100%);
}
.cbox[data-on="palettes"] .cpanel {
  --cl: calc(var(--tw) + var(--tg) + 1px); --cr: calc(2 * var(--tw) + var(--tg) - 1px);
  border-top-left-radius: var(--radius);
}
.cpanel .ctools { margin-top: 0; }
.cpanel .empty { margin: 0; }

/* ---- the palette designer (palettes.js) ------------------------------------
   The effect designer's frame (.tlc.design): its lamp and Show on a light,
   and the gradient being made beside them - the bar, a marker under it for
   each colour, and the chosen colour's wheel, hex and position. */
.pald .pd-body { padding-top: 16px; }
.pd-bar { height: 48px; border-radius: 10px; cursor: copy;
          box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 20%, transparent); }
.pd-marks { position: relative; height: 26px; margin: 6px 12px 4px; }
.pd-mark {
  position: absolute; top: 0; transform: translateX(-50%);
  width: 22px; height: 22px; padding: 0; border-radius: 50%; cursor: grab; touch-action: none;
  background: var(--c); border: 2px solid var(--panel);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--fg) 35%, transparent);
}
.pd-mark:hover { filter: none; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 60%, transparent); }
.pd-mark.fixed { cursor: pointer; }
.pd-mark.on { box-shadow: 0 0 0 3px var(--accent); }
.pd-hint { margin: 0 0 14px; min-height: 1.3em; }
.pd-stop { display: grid; grid-template-columns: minmax(0, 240px) minmax(0, 1fr); gap: 18px; align-items: start; }
.pd-fields { display: grid; gap: 12px; align-content: start; }
.pd-fields .tlc-hexrow span { flex: 0 0 64px; }
.pd-pos { width: 90px; }
.pd-del { justify-self: start; padding: 0; }
.pd-del:disabled { opacity: .4; cursor: default; }
@media (max-width: 560px) {
  .pd-stop { grid-template-columns: 1fr; }
}

/* ---- switched to another user (Account settings, Switch user) ----------- */
.switchbar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 4px 12px;
  padding: 8px 16px; font-size: 14px; text-align: center;
  background: color-mix(in srgb, var(--warn) 20%, var(--bg));
  border-bottom: 1px solid color-mix(in srgb, var(--warn) 50%, transparent);
}
.switchbar form { margin: 0; }
.switchbar .link { font-weight: 600; }
.switchform { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 12px; }
.switchform label { flex: 1 1 220px; margin: 0; }

/* The effect designer's "What is it for?" (control.js, askKind): one button
   per kind of light. */
.tlc-kinds { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; margin: 14px 0 6px; }
.tlc-kinds button { padding: 14px 10px; }

/* Account settings' REST API card (Handler._api_card): the usage graph
   (account.js), a token just made, the tokens and a new one. */
.apiuse { margin: 14px 0 6px; }
.apiuse canvas { display: block; width: 100%; height: 110px; }
.apiuse-text { margin: 6px 0 0; }
.apinew { margin: 12px 0; padding: 10px 12px; border: 1px solid var(--ok); border-radius: var(--radius); background: var(--card-2); }
.apinew p { margin: 0 0 6px; }
.apinew .secret { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 0; }
.apinew code { word-break: break-all; user-select: all; }
.apilist { margin: 12px 0; display: grid; gap: 8px; }
.apitok { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 2px 12px;
  padding: 8px 0; border-top: 1px solid var(--line); }
.apitok .apiname { display: flex; gap: 8px; align-items: center; margin: 0; }
.apitok .apiname input { flex: 1 1 auto; min-width: 0; max-width: 320px; }
.apitok > span { grid-column: 1; }
.apitok .apidel { grid-column: 2; grid-row: 1 / span 2; margin: 0; }
.apinewform { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 12px; }
.apinewform label { flex: 1 1 220px; margin: 0; }

/* The guide's code examples (docs.py, ``` blocks - the REST API page). */
.docs-code { margin: 12px 0; padding: 10px 14px; overflow-x: auto; background: var(--card-2);
  border: 1px solid var(--line); border-radius: var(--radius); font-size: 13px; line-height: 1.5; }
.docs-code code { background: none; padding: 0; white-space: pre; }

/* Ableton Link: a light's line under its switches (the Light menu), and the
   group's tempo beside the group switches - hidden while it is off. */
.linkst { margin: 8px 0 0; font-size: 12.5px; color: var(--muted); }
.tag.link {
  font-size: 11px; padding: 1px 7px; border-radius: 999px; white-space: nowrap;
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}
.tag.link[hidden] { display: none; }
