/* OPMON - one stylesheet, no CDN, no build step.
   Spec: "0 - PLAN/6 - Web UI Spec.txt" 6.1 */

:root{
  --bg:#0f1115; --panel:#171a21; --panel2:#1c2029; --line:#262b36;
  --text:#e8eaed; --muted:#9aa0a6; --dim:#6b7280;
  --ok:#34d399; --bad:#f87171; --warn:#fbbf24; --accent:#60a5fa;
  --radius:10px;
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
}

*,*::before,*::after{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  background:var(--bg); color:var(--text);
  font:14px/1.6 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  -webkit-font-smoothing:antialiased;
}
body.centered{display:flex;align-items:center;justify-content:center;min-height:100vh;padding:24px}

a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code,pre,.mono,.num{font-family:var(--mono)}
.num{font-variant-numeric:tabular-nums}
h1,h2,h3{margin:0 0 10px;line-height:1.3}
h1{font-size:22px}h2{font-size:17px}h3{font-size:14px}
p{margin:0 0 12px}
hr{border:0;border-top:1px solid var(--line);margin:20px 0}

.muted{color:var(--muted)}
.dim{color:var(--dim)}
.small{font-size:12px}
.center{text-align:center}
.ok{color:var(--ok)}.bad{color:var(--bad)}.warn{color:var(--warn)}
.right{text-align:right}
.nowrap{white-space:nowrap}
.row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
.row-tight{display:flex;gap:6px;align-items:center}
.spacer{flex:1}
.stack>*+*{margin-top:14px}

/* ---------- shell ---------- */
.topbar{
  display:flex;align-items:center;gap:20px;
  padding:0 20px;height:52px;
  background:var(--panel);border-bottom:1px solid var(--line);
  position:sticky;top:0;z-index:50;
}
.brand{display:flex;align-items:center;gap:8px;color:var(--text);font-weight:700;letter-spacing:.02em}
.brand:hover{text-decoration:none}
.logo{color:var(--accent);font-size:18px}
.logo.lg{font-size:34px;display:block;margin-bottom:6px}
.tabs{display:flex;gap:2px;overflow-x:auto}
.tabs a{
  color:var(--muted);padding:7px 13px;border-radius:7px;font-size:13px;
  white-space:nowrap;font-weight:500;
}
.tabs a:hover{background:var(--panel2);color:var(--text);text-decoration:none}
.tabs a.on{background:var(--panel2);color:var(--text)}
.topright{margin-left:auto;display:flex;align-items:center;gap:12px}

/* ---------- impersonation ---------------------------------------------------
   The picker sits between the brand and the tabs, so the account you are
   looking at is next to the name of the app rather than lost on the right.
   When it is active everything about it turns amber - the one state where the
   page you are reading is not your own. */
.imp{position:relative;flex:0 0 auto}
.imp input{
  width:190px;height:30px;padding:0 10px;font-size:12.5px;
  background:var(--panel2);border:1px solid var(--line);border-radius:7px;color:var(--text);
}
.imp input:focus{outline:none;border-color:var(--accent)}
.imp.imp-on input{border-color:var(--warn);color:var(--warn)}
.imp-list{
  position:absolute;top:36px;left:0;width:300px;z-index:120;
  background:var(--panel);border:1px solid var(--line);border-radius:8px;
  box-shadow:0 10px 30px var(--shadow-color);max-height:330px;overflow-y:auto;padding:4px;
}
.imp-item{padding:7px 10px;border-radius:6px;cursor:pointer;font-size:13px}
.imp-item:hover{background:var(--panel2)}
.imp-item strong{display:block;font-weight:600}
.imp-item span{display:block;color:var(--dim);font-size:11.5px}
.imp-item.imp-master strong{color:var(--accent)}
.imp-empty{padding:10px;color:var(--dim);font-size:12.5px}
.imp-bar{
  background:var(--warn-bg);border-bottom:1px solid var(--warn-line);color:var(--warn-text);
  padding:8px 20px;font-size:13px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;
}
.imp-bar .btn{margin-left:auto}

.wrap{max-width:1280px;margin:0 auto;padding:22px 20px 80px}

/* ---------- panels ---------- */
.panel{
  background:var(--panel);border:1px solid var(--line);
  border-radius:var(--radius);padding:20px;margin-bottom:16px;
}
.panel-head{display:flex;align-items:flex-start;gap:14px;margin-bottom:14px;flex-wrap:wrap}
/* Controls must never be squeezed by a wordy sibling. Flex items shrink by
   default, which once crushed the Standby Publish Target and retry boxes to
   a sliver that could not be clicked into. They wrap to the next line
   instead of shrinking. */
.panel-head>.row-tight{flex-shrink:0;flex-wrap:wrap}
.panel-head input[type=number]{min-width:72px}
.panel-head h2{margin:0}
.panel-sub{color:var(--muted);font-size:13px;margin:2px 0 0}
.panel-tight{padding:14px 16px}
.grid{display:grid;gap:16px}
.grid-2{grid-template-columns:repeat(auto-fit,minmax(340px,1fr))}
.grid-3{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
.grid-4{grid-template-columns:repeat(auto-fit,minmax(190px,1fr))}
.grid-cards{grid-template-columns:repeat(auto-fill,minmax(260px,1fr))}

/* ---------- forms ---------- */
label{display:block;font-size:12px;color:var(--muted);margin:0 0 5px;font-weight:600}
input[type=text],input[type=email],input[type=password],input[type=number],
input[type=url],select,textarea{
  width:100%;background:var(--bg);color:var(--text);
  border:1px solid var(--line);border-radius:7px;
  padding:9px 11px;font:inherit;outline:none;
}
textarea{resize:vertical;min-height:80px;line-height:1.5}
input:focus,select:focus,textarea:focus{border-color:var(--accent)}
input:disabled,select:disabled,textarea:disabled{opacity:.55;cursor:not-allowed}
input::placeholder,textarea::placeholder{color:var(--dim)}
.field{margin-bottom:14px}
.field-help{font-size:12px;color:var(--dim);margin-top:4px}
select{appearance:none;background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),linear-gradient(135deg,var(--muted) 50%,transparent 50%);background-position:calc(100% - 16px) 52%,calc(100% - 11px) 52%;background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:32px}

.btn{
  display:inline-flex;align-items:center;gap:6px;justify-content:center;
  background:var(--panel2);color:var(--text);
  border:1px solid var(--line);border-radius:7px;
  padding:8px 14px;font:inherit;font-weight:600;font-size:13px;
  cursor:pointer;white-space:nowrap;
}
.btn:hover{border-color:var(--line-strong);text-decoration:none}
.btn:disabled{opacity:.5;cursor:not-allowed}
.btn-primary{background:var(--accent);border-color:var(--accent);color:var(--accentInk)}
.btn-primary:hover{background:var(--accent-hover);border-color:var(--accent-hover)}
.btn-ghost{background:transparent}
.btn-danger{color:var(--bad);border-color:var(--bad-line)}
.btn-danger:hover{background:var(--bad-bg)}
.btn-sm{padding:5px 10px;font-size:12px}
.btn-block{width:100%;padding:11px}

/* ---------- login ---------- */
.login{
  width:100%;max-width:380px;background:var(--panel);
  border:1px solid var(--line);border-radius:14px;padding:30px;
}
.login-head{text-align:center;margin-bottom:22px}
.login-head h1{margin:0 0 2px;font-size:20px}
.login-head p{margin:0;font-size:13px}
.login label{margin-top:12px}
.login .btn{margin-top:20px}

.alert{padding:10px 13px;border-radius:7px;font-size:13px;margin-bottom:14px;border:1px solid}
.alert-bad{background:var(--bad-bg);border-color:var(--bad-line);color:var(--bad-text)}
.alert-warn{background:var(--warn-bg);border-color:var(--warn-line);color:var(--warn-text)}
.alert-ok{background:var(--ok-bg);border-color:var(--ok-line);color:var(--ok-text)}
.alert-info{background:var(--info-bg);border-color:var(--info-line);color:var(--info-text)}

/* ---------- pills & dots ---------- */
.pill{
  display:inline-flex;align-items:center;gap:5px;
  padding:3px 9px;border-radius:999px;
  font-size:11px;font-weight:700;letter-spacing:.03em;white-space:nowrap;
  border:1px solid;
}
.pill i{font-style:normal;font-size:11px;line-height:1}
.pill-ok{color:var(--ok);border-color:var(--ok-line);background:var(--ok-bg)}
.pill-bad{color:var(--bad);border-color:var(--bad-line);background:var(--bad-bg)}
.pill-warn{color:var(--warn);border-color:var(--warn-line);background:var(--warn-bg)}
.pill-mute{color:var(--dim);border-color:var(--line);background:var(--panel2)}

.dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--dim)}
.dot-ok{background:var(--ok)}.dot-bad{background:var(--bad)}
.dot-warn{background:var(--warn)}.dot-mute{background:var(--dim)}
.badge{display:inline-block;padding:2px 7px;border-radius:5px;background:var(--panel2);
  border:1px solid var(--line);font-size:11px;color:var(--muted);font-weight:600}

/* ---------- credential / engine rows ---------- */
.cred{
  display:grid;grid-template-columns:220px 1fr auto auto;
  gap:12px;align-items:center;
  padding:12px 0;border-bottom:1px solid var(--line);
}
.cred:last-child{border-bottom:0}
.cred-label{font-weight:600;font-size:13px}
.cred-unlocks{font-size:11px;color:var(--dim);margin-top:2px;line-height:1.4}
.cred-input{position:relative}
.cred-result{min-width:135px;text-align:right}
@media (max-width:820px), (max-height:500px){
  .cred{grid-template-columns:1fr;gap:6px}
  .cred-result{text-align:left}
}

/* ---------- tables ---------- */
table.data{width:100%;border-collapse:collapse;font-size:13px}
table.data th{
  text-align:left;color:var(--muted);font-size:11px;font-weight:700;
  letter-spacing:.05em;text-transform:uppercase;
  padding:7px 10px 7px 0;border-bottom:1px solid var(--line);
}
table.data td{padding:8px 10px 8px 0;border-bottom:1px solid var(--line);vertical-align:top}
table.data tr:last-child td{border-bottom:0}
.scroll-x{overflow-x:auto}

/* ---------- log ---------- */
.log{
  background:var(--code-bg);border:1px solid var(--line);border-radius:8px;
  padding:12px;font-family:var(--mono);font-size:12px;line-height:1.65;
  max-height:520px;overflow-y:auto;
}
.log-line{display:flex;gap:9px;padding:1px 0}
.log-time{color:var(--dim);flex:none}
.log-msg{white-space:pre-wrap;word-break:break-word}
.log-warn .log-msg{color:var(--warn)}
.log-error .log-msg{color:var(--bad)}
.log-tool .log-msg{color:var(--accent)}

/* ---------- progress ---------- */
.bar{height:6px;background:var(--panel2);border-radius:99px;overflow:hidden}
.bar>span{display:block;height:100%;background:var(--accent);transition:width .3s}
.bar.over>span{background:var(--bad)}

/* ---------- toasts ---------- */
.toasts{position:fixed;right:18px;bottom:18px;display:flex;flex-direction:column;gap:8px;z-index:200}
.toast{
  background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--accent);
  border-radius:8px;padding:10px 14px;font-size:13px;max-width:380px;
  box-shadow:0 8px 24px var(--shadow-color);animation:slidein .18s ease-out;
}
.toast.ok{border-left-color:var(--ok)}
.toast.bad{border-left-color:var(--bad)}
.toast.warn{border-left-color:var(--warn)}
@keyframes slidein{from{opacity:0;transform:translateX(14px)}to{opacity:1;transform:none}}

/* ---------- misc ---------- */
.spin{display:inline-block;width:11px;height:11px;border:2px solid var(--line);
  border-top-color:var(--accent);border-radius:50%;animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.kv{display:grid;grid-template-columns:auto 1fr;gap:5px 14px;font-size:13px}
.kv dt{color:var(--muted)}
.kv dd{margin:0}
video{width:100%;border-radius:8px;background:#000;display:block}
.empty{padding:36px 20px;text-align:center;color:var(--dim)}

/* Failure explanation under a test pill. Deliberately readable rather than
   tucked into a title= tooltip - see view.php::pill(). */
.pill-why{
  margin-top:6px;font-size:11px;line-height:1.5;
  max-width:340px;margin-left:auto;text-align:left;
  white-space:normal;word-break:break-word;
}
.cred-result .pill-why{margin-left:auto}
@media (max-width:820px), (max-height:500px){ .pill-why{margin-left:0;max-width:none} }

/* ---------- library preview ------------------------------------------------
   The player used to be as wide as the column let it be, which made a page of
   twelve finished videos a page of twelve enormous black rectangles you had to
   scroll past. A finished video is identified by its title and its thumbnail,
   not by a full-size player, so the preview is now a thumbnail-sized one and
   the card is a row rather than a poster. */
.lib-player{
  width:100%;max-width:180px;aspect-ratio:9/16;
  object-fit:cover;border-radius:8px;background:#000;
}
.lib-card{display:grid;grid-template-columns:180px 1fr;gap:18px;align-items:start}
@media (max-width:700px){
  .lib-card{grid-template-columns:1fr}
  .lib-player{max-width:140px}
}

/* ---------- modal ---------- */
.modal-back{
  position:fixed;inset:0;background:var(--overlay);z-index:300;
  display:flex;align-items:flex-start;justify-content:center;
  padding:40px 18px;overflow-y:auto;
}
.modal{
  background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
  width:100%;max-width:760px;box-shadow:0 24px 60px var(--shadow-color);
}
.modal.wide{max-width:1000px}
.modal-head{
  display:flex;align-items:center;gap:12px;
  padding:14px 18px;border-bottom:1px solid var(--line);
}
.modal-body{padding:18px;max-height:calc(100vh - 190px);overflow-y:auto}
.modal-foot{
  display:flex;align-items:center;gap:8px;
  padding:12px 18px;border-top:1px solid var(--line);
}
.panel-inset{
  background:var(--panel2);border:1px solid var(--line);border-radius:8px;
  padding:12px 14px;margin-bottom:10px;
}

/* ---------- status bar -----------------------------------------------------
   Fixed to the bottom so it is visible from every scroll position: the point
   of a status bar is that you do not have to go looking for it to find out
   something failed. */
/* ---------- Que & Processing strip ------------------------------------------
   Fixed to the very bottom, under the Contents status bar. Both are fixed, so
   they cannot overlap by accident: the strip publishes its own height as
   --opsbar-h and the status bar and the page body are lifted by it. */
.opsbar{
  position:fixed;left:0;right:0;bottom:0;z-index:160;
  background:var(--panel);border-top:1px solid var(--line);
  box-shadow:0 -6px 20px var(--shadow-color);
  padding:6px 16px 8px;font-size:12.5px;
}
.ops-meters{display:flex;flex-wrap:wrap;align-items:center;gap:18px;font-size:12px;margin-bottom:5px}
.ops-meter{display:flex;align-items:center;gap:6px;white-space:nowrap}
.ops-meter b{color:var(--dim);font-weight:600;letter-spacing:.04em;font-size:10px}
/* The bar is what makes two numbers comparable at a glance; the number alone
   gets read as a label. */
.ops-gauge{
  display:inline-block;width:52px;height:5px;border-radius:3px;
  background:var(--line);overflow:hidden;
}
.ops-gauge i{display:block;height:100%;background:var(--ok,#34d399);transition:width .3s ease}
.ops-meter.warm .ops-gauge i{background:var(--warn,#fbbf24)}
.ops-meter.hot  .ops-gauge i{background:var(--bad,#f87171)}
.ops-meter.hot b{color:var(--bad,#f87171)}
.ops-live{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.ops-chip{
  display:inline-flex;align-items:center;gap:6px;
  border:1px solid var(--line);background:var(--panel2);
  border-radius:999px;padding:2px 10px;white-space:nowrap;font-size:11.5px;
}
.ops-chip b{font-weight:600}
.ops-chip.live{border-color:var(--ok-line)}
.ops-chip.queued{border-style:dashed;color:var(--dim)}

.ops-queue{margin-top:4px}
.ops-queue>summary{
  list-style:none;cursor:pointer;display:flex;align-items:center;gap:10px;
  padding:2px 0;user-select:none;
}
.ops-queue>summary::-webkit-details-marker{display:none}
.ops-title{font-weight:600;letter-spacing:.02em}
.ops-caret{
  display:inline-block;color:var(--dim);
  transition:transform .12s ease;transform-origin:50% 50%;
}
.ops-queue[open] .ops-caret{transform:rotate(90deg)}
.ops-queue>summary:hover .ops-caret,.ops-queue>summary:hover .ops-title{color:var(--accent)}
.ops-hint{margin-left:auto}
.ops-body{max-height:46vh;overflow:auto;padding:6px 0 4px}
.ops-body h5{margin:10px 0 4px;font-size:12px;color:var(--muted)}
.ops-engines{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
.ops-engine{background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:8px 10px}
.ops-engine h5{margin:0 0 4px}
.ops-table td{padding:3px 6px;font-size:11.5px}
.ops-table tr.row-live td{background:color-mix(in srgb,var(--ok) 8%,transparent)}
.tag{
  display:inline-block;padding:1px 6px;border-radius:5px;font-size:10.5px;
  background:var(--panel);border:1px solid var(--line);color:var(--dim);
}
.tag-ok{color:var(--ok);border-color:var(--ok-line)}
.tag-warn{color:var(--warn);border-color:var(--warn-line)}

body{padding-bottom:var(--opsbar-h,0)}

.statusbar{
  position:fixed;left:0;right:0;bottom:var(--opsbar-h,0);z-index:150;
  background:var(--panel);border-top:1px solid var(--line);
  box-shadow:0 -6px 20px var(--shadow-color);
}
.statusbar-head{
  display:flex;align-items:center;gap:12px;
  padding:8px 18px;cursor:pointer;user-select:none;
}
.statusbar-body{
  border-top:1px solid var(--line);padding:12px 18px;
  max-height:44vh;overflow-y:auto;
}
.statusbar-log{
  background:var(--code-bg);border:1px solid var(--line);border-radius:8px;
  padding:10px 12px;font-family:var(--mono);font-size:12px;line-height:1.6;
  max-height:30vh;overflow:auto;white-space:pre-wrap;word-break:break-word;
}
/* Colour is never the only signal: each state also carries its own word. */
.state{
  display:inline-flex;align-items:center;gap:7px;
  font-weight:700;font-size:12px;letter-spacing:.06em;
}
.state-dot{width:9px;height:9px;border-radius:50%;flex:none}
.state-working{color:var(--bad)}
.state-working .state-dot{background:var(--bad);animation:pulse 1.2s ease-in-out infinite}
.state-free{color:var(--ok)}
.state-free .state-dot{background:var(--ok)}
@keyframes pulse{50%{opacity:.35}}
/* The bar floats over the page; without this the last panel hides under it. */
body.has-statusbar main.wrap{padding-bottom:78px}

/* ---------- storage meter ---------- */
.meter-row{display:grid;grid-template-columns:190px 1fr auto auto;gap:14px;align-items:center;
  padding:10px 0;border-bottom:1px solid var(--line)}
.meter-row:last-child{border-bottom:0}
@media (max-width:760px){ .meter-row{grid-template-columns:1fr auto} }

/* ---------- queue ---------- */
.queue-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted)}
.queue-item{
  display:flex;gap:9px;align-items:baseline;padding:6px 0;
  border-bottom:1px solid var(--line);font-size:13px;
}
.queue-item:last-child{border-bottom:0}
.queue-pos{color:var(--dim);font-family:var(--mono);flex:none}

/* ---------- storage meter --------------------------------------------------
   A single bar whose COLOUR carries the same message as its width: green with
   room to spare, amber when it is getting tight, red when a render is about to
   have nowhere to land. The percentage is written on it as well, because
   colour alone is not a signal anyone should have to rely on. */
.meter{
  position:relative;height:22px;border-radius:99px;overflow:hidden;
  background:var(--panel2);border:1px solid var(--line);
}
.meter>span{
  display:block;height:100%;border-radius:99px;
  transition:width .35s ease, background .35s ease;
  background:linear-gradient(90deg,#34d399,#a3e635);
}
.meter.warn>span{background:linear-gradient(90deg,#a3e635,#fbbf24)}
.meter.high>span{background:linear-gradient(90deg,#fbbf24,#fb923c)}
.meter.full>span{background:linear-gradient(90deg,#fb923c,#f87171)}
.meter-label{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;
  color:#0b0d11;text-shadow:0 1px 0 rgba(255,255,255,.35);
  letter-spacing:.02em;pointer-events:none;
}

/* ---- Published, folded ---------------------------------------------------
   Closed on load. It is the longest table on the tab and the least often
   acted on - a record rather than a queue - and the Auto Clear controls that
   sit above it stay visible either way. */
.published-fold > summary {
  cursor: pointer;
  list-style: none;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel2);
  font-size: 13px;
  color: var(--muted);
  user-select: none;
}
.published-fold > summary::-webkit-details-marker { display: none; }
.published-fold > summary:hover { color: var(--text); border-color: var(--accent); }
.published-fold > summary::before {
  content: '\25B8';           /* right-pointing triangle when closed */
  display: inline-block;
  margin-right: 8px;
  transition: transform .12s ease;
}
.published-fold[open] > summary::before { transform: rotate(90deg); }
.published-fold[open] > summary { margin-bottom: 10px; }
.published-fold .published-summary-hide { display: none; }
.published-fold[open] .published-summary-show { display: none; }
.published-fold[open] .published-summary-hide { display: inline; }

/* ---- Storage remaining, on the status bar --------------------------------
   Green while there is room, amber as it runs down, red when a render can no
   longer land. The thresholds come from the account's own Min Storage. */
.store-chip {
  font-variant-numeric: tabular-nums;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 12px;
  white-space: nowrap;
}
.store-chip.ok   { color: var(--ok);   border-color: color-mix(in srgb, var(--ok) 40%, transparent); }
.store-chip.warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.store-chip.bad  { color: var(--bad);  border-color: color-mix(in srgb, var(--bad) 50%, transparent); }

/* ---- sortable date columns -----------------------------------------------
   The arrow is the state, not decoration: it says which way the rows are
   ordered right now, and it is present before anything is clicked because the
   rows are actually sorted on load. The server returns them in id order, which
   is not the same as date order, so the arrow would otherwise disagree with
   the rows underneath it. */
th.sort-date {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}
th.sort-date:hover { color: var(--text); }
th.sort-date::after {
  content: '\25BC';                 /* down: newest first */
  font-size: 9px;
  margin-left: 6px;
  opacity: .55;
}
th.sort-date[data-dir="asc"]::after { content: '\25B2'; }   /* up: oldest first */
th.sort-date:hover::after { opacity: 1; }
th.sort-date:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ---- video preview -------------------------------------------------------
   FIT INSIDE THE BOX, WHATEVER SHAPE THE VIDEO IS.

   The preview used to set max-height only, so a 9:16 video fitted and a 16:9
   one ran off both sides of the modal with its edges cut off. Constraining
   BOTH axes and letting the other side follow means portrait, landscape and
   square all sit fully inside the frame. Maximise gives the same rule more
   room rather than a different rule. */
#view-modal .modal{width:min(1100px,92vw)}
#view-modal .view-media{
  display:block;margin:0 auto;
  max-width:100%;max-height:66vh;width:auto;height:auto;
  background:#000;border-radius:8px;
}
#view-modal.is-max .modal{width:96vw;max-width:96vw}
#view-modal.is-max .view-media{max-height:82vh}

/* ---- Buffered video preview -------------------------------------------
   The edited files run about 10 Mbps, so on any connection slower than that
   progressive playback can never keep up and the picture stops every few
   seconds. The preview therefore downloads the whole file before it plays
   and keeps it, which is why there is a progress bar here at all. */
#view-modal .vbuf{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:14px;min-height:280px;padding:28px 20px;text-align:center;
}
#view-modal .vbuf-bar{
  width:min(420px,80%);height:6px;border-radius:999px;overflow:hidden;
  background:rgba(127,127,127,.25);
}
#view-modal .vbuf-fill{
  display:block;height:100%;width:0;border-radius:999px;
  background:currentColor;transition:width .18s linear;
}
#view-modal .vbuf-pct{font-variant-numeric:tabular-nums;font-weight:600}
#view-modal .vbuf-sub{opacity:.65;font-size:12px;max-width:46ch;line-height:1.5}
#view-modal .vbuf-now{
  background:none;border:0;padding:0;font:inherit;font-size:12px;
  color:inherit;opacity:.75;text-decoration:underline;cursor:pointer;
}
#view-modal .vbuf-now:hover{opacity:1}

/* =========================================================================
   PHONES (max-width 820px) - nothing above it changes.
   Measured 2026-09-15 at 390x844: the header's one 52px row (brand, picker,
   twelve tabs, email, Sign out) and the render queue table pushed the page to
   807px, so Chrome zoomed the whole application out and nothing could be read
   or tapped.
   ========================================================================= */
.nav-toggle,.nav-current{display:none}

@media (max-width:820px), (max-height:500px){
  /* Below 16px iOS zooms in on focus and never zooms back. */
  input[type=text],input[type=email],input[type=password],input[type=number],
  input[type=url],input[type=datetime-local],select,textarea{font-size:16px}

  /* ---- header: menu button, app, current tab ---- */
  .topbar{height:56px;padding:0 10px;gap:10px}
  .nav-toggle{
    display:grid;place-items:center;flex:none;width:42px;height:42px;
    background:var(--panel2);border:1px solid var(--line);border-radius:10px;
    color:var(--text);font-size:19px;line-height:1;cursor:pointer;
  }
  .brand{flex:none}
  .nav-current{display:block;flex:1 1 auto;min-width:0;color:var(--muted);font-weight:600;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .topbar>.imp,.topbar>.tabs,.topbar>.topright{display:none}

  /* ---- the menu, full screen ---- */
  body.nav-open{overflow:hidden}
  body.nav-open .topbar{
    position:fixed;inset:0;height:auto;z-index:400;
    flex-wrap:wrap;align-content:flex-start;row-gap:14px;
    padding:7px 10px 28px;overflow-y:auto;
  }
  body.nav-open .topbar>.tabs,body.nav-open .topbar>.topright{display:flex;flex:0 0 100%}
  body.nav-open .topbar>.imp{display:block;flex:0 0 100%}
  .imp input{width:100%;height:44px}
  .imp-list{width:100%;max-height:50vh}
  body.nav-open .tabs{flex-direction:column;gap:4px;overflow:visible}
  body.nav-open .tabs a{padding:12px 14px;font-size:15px;border-radius:9px;background:var(--panel2)}
  body.nav-open .tabs a.on{box-shadow:inset 0 0 0 1px var(--accent);color:var(--text)}
  body.nav-open .topright{flex-wrap:wrap;gap:10px;margin-left:0;padding-top:12px;border-top:1px solid var(--line)}
  .imp-bar{padding:8px 12px}

  /* ---- page ---- */
  .wrap{padding:12px 10px 90px}
  .panel{padding:14px;margin-bottom:12px}
  .grid-2,.grid-3,.grid-4,.grid-cards,.ops-engines{grid-template-columns:minmax(0,1fr)}
  .row-tight,.panel-head,.modal-head,.modal-foot,.log-line{flex-wrap:wrap}
  .kv{grid-template-columns:minmax(0,max-content) minmax(0,1fr)}
  .kv dd{overflow-wrap:anywhere}
  pre{max-width:100%;overflow-x:auto}
  img,video{max-width:100%}
  .panel-inset{padding:10px 12px}
  /* Desktop keeps these control rows from shrinking (flex:0 0 auto, above) so a
     wordy title cannot crush them. On a phone that same rule held them at their
     one-line width - 374 to 751px on Contents - so here they take the full row
     and wrap instead. */
  .panel-head>.row-tight{flex:1 1 100%;min-width:0}
  .panel select,.panel input,.panel textarea{max-width:100%}
  /* The two fixed bottom bars wrapped to ~430px - half the screen. Each is one
     line on a phone: overflowing text is truncated or scrolls sideways. */
  .statusbar-head{flex-wrap:nowrap}
  .statusbar-head .spacer{display:none}
  #state-summary{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  #store-chip{margin-left:auto}
  .ops-meters{flex-wrap:nowrap;overflow-x:auto;margin-bottom:2px}
  .ops-queue>summary{flex-wrap:nowrap}
  .ops-title{white-space:nowrap}
  #ops-summary{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* Small inline links get a finger-sized hit area without moving the text. */
  .panel p a:not(.btn),.panel td a:not(.btn),.panel .small a:not(.btn),.field-help a,.panel-sub a,
  .cred a:not(.btn){
    display:inline-block;padding:8px 0;margin:-8px 0;
  }

  /* Tables scroll inside themselves instead of widening the page. */
  table.data,#quepanel .qp{display:block;max-width:100%;overflow-x:auto}

  /* Fingers, not pointers. A long button label wraps rather than setting the
     page width; inside a table it stays on one line and the table scrolls. */
  .btn{white-space:normal;min-height:40px}
  .btn-sm{min-height:36px}
  table .btn{white-space:nowrap}

  /* ---- dialogs, toasts ---- */
  .modal-back{padding:8px}
  .modal-body{max-height:calc(100dvh - 170px)}
  #view-modal .modal,#view-modal.is-max .modal{width:100%;max-width:100%}
  .toasts{left:10px;right:10px;bottom:10px}
  .toast{max-width:none}

  /* ---- the bottom strips ---- */
  .opsbar{padding:6px 10px}
  .ops-meters{gap:4px 12px}
  .ops-meter .muted.small{display:none}
  .ops-live{flex-wrap:nowrap;overflow-x:auto}
  .ops-body{max-height:40vh}
  .statusbar-head{flex-wrap:wrap;padding:8px 12px}
  .statusbar-body{padding:10px 12px}
}

/* ---- Explainator brand (2026-09-29): electric violet --------------------- */
/* colours: site/app-theme.css (generated from shared/site/brands.php) */
.brand-mark { width: 52px; height: 52px; border-radius: 13px; display: block; margin: 0 auto 10px; }
.site-back { text-align: center; margin-top: 16px; font-size: 13px; }
.site-back a { color: var(--muted); }

/* ---- accounts: terms tick, Google button (2026-09-29) --------------------- */
.terms-check { display: flex; gap: 10px; align-items: flex-start; margin: 14px 0 6px; font-size: 13.5px; line-height: 1.45; cursor: pointer; }
.terms-check input { width: 18px; height: 18px; margin-top: 1px; flex: none; accent-color: var(--accent); }
.or-sep { display: flex; align-items: center; gap: 12px; color: var(--muted); font-size: 12.5px; margin: 14px 0; }
.or-sep::before, .or-sep::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.btn-google { width: 100%; display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 42px; border-radius: 8px;
  border: 1px solid var(--line); background: #fff; color: #1f1f1f; font: 600 14px system-ui, sans-serif; cursor: pointer; }
.btn-google:hover { background: #f3f4f6; }

/* Choose-a-plan strip (shared/account/billingflow.php bf_banner) */
.plan-banner { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 12px 0; padding: 12px 16px; border-radius: 12px; border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); background: color-mix(in srgb, var(--accent) 12%, transparent); font-size: 14.5px; }
.plan-banner.warn { border-color: var(--warn-line); background: var(--warn-bg); }
.plan-banner-btn { display: inline-block; padding: 8px 14px; border-radius: 8px; background: var(--accent); color: var(--accentInk); font-weight: 600; text-decoration: none; white-space: nowrap; }

/* =========================================================================
   THE PUBLIC SITE'S LOOK (2026-09-30)
   Colours come from /site/app-theme.css, generated from shared/site/brands.php
   - the same palette the homepage uses, in dark and light, switched by the
   same 'av-theme' choice (site/theme.js). This block only carries the shapes
   and type that make the dashboard read as the same product: Inter for text,
   the brand's display face for headings, the frosted nav, pill buttons.
   ========================================================================= */
body{font-family:var(--ui,system-ui,sans-serif);font-size:14px}
h1,h2,h3,h4,.login-head h1{font-family:var(--display,inherit);letter-spacing:-.01em;font-weight:650}
a{color:var(--accent-text,var(--accent))}
input[type=checkbox],input[type=radio],input[type=range],progress{accent-color:var(--accent)}

/* ---- the top bar: the site's frosted nav ---- */
.topbar{
  background:color-mix(in srgb,var(--bg) 84%,transparent);
  -webkit-backdrop-filter:saturate(160%) blur(14px);backdrop-filter:saturate(160%) blur(14px);
  border-bottom:1px solid var(--line);height:58px;
}
.brand{font-family:var(--display,inherit);font-size:17px;letter-spacing:-.01em;gap:9px}
.brand-logo{width:26px;height:26px;border-radius:7px;display:block;flex:none}
.tabs a{border-radius:999px;padding:7px 13px}
.tabs a:hover{background:var(--hover-strong)}
.tabs a.on{background:var(--accent-soft);color:var(--accent-text,var(--accent))}
.topbar>.theme-toggle{order:2;margin-left:auto}
.topbar>.theme-toggle~.topright{order:3;margin-left:12px}

/* ---- buttons: pills, like the site ---- */
.btn{border-radius:999px;padding:8px 16px;transition:background .15s ease,border-color .15s ease,box-shadow .2s ease}
.btn:hover{background:var(--surface3)}
.btn-ghost:hover{background:var(--hover-strong)}
.btn-primary{box-shadow:0 8px 24px -10px var(--glow),inset 0 -2px 0 rgba(0,0,0,.12)}
.btn-primary:hover{background:var(--accent-hover);box-shadow:0 12px 30px -10px var(--glow),inset 0 -2px 0 rgba(0,0,0,.12)}
.btn-danger:hover{background:var(--bad-bg)}
.btn-sm{padding:5px 12px}
.btn-xs{padding:3px 9px}
.plan-banner-btn{border-radius:999px}

/* ---- cards, fields ---- */
.panel{border-radius:14px}
.modal{border-radius:16px;box-shadow:var(--shadow)}
.toast{box-shadow:var(--shadow-sm)}
input[type=text],input[type=email],input[type=password],input[type=number],input[type=url],
input[type=search],input[type=tel],input[type=time],input[type=date],input[type=datetime-local],
input[type=file],select,textarea{background-color:var(--field);border-color:var(--line);border-radius:10px}
input:focus,select:focus,textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.imp input{background:var(--field)}

/* ---- sign-in, sign-up, reset: the site's hero glow behind the card ---- */
body.centered{background:radial-gradient(ellipse 80% 55% at 50% -10%,var(--glow),transparent 70%),var(--bg);background-attachment:fixed}
.login{border-radius:18px;box-shadow:var(--shadow-sm)}
.theme-float{position:fixed;top:14px;right:14px;z-index:10;width:42px;height:42px;background:var(--surface)}

@media (max-width:820px), (max-height:500px){
  .topbar{height:56px}
  .topbar>.theme-toggle{order:0;margin-left:0;width:42px;height:42px}
  /* The open menu is a full screen: no see-through, whatever the bar does. */
  body.nav-open .topbar{background:var(--bg);-webkit-backdrop-filter:none;backdrop-filter:none}
  body.nav-open .tabs a{border-radius:12px}
  body.nav-open .tabs a.on{background:var(--accent-soft);box-shadow:inset 0 0 0 1px var(--accent-line)}
  .btn{padding:8px 14px}
}

/* The signed-in address: one line, never the thing that wraps the bar. It
   gives way first when the tabs need the room. */
.topright>.muted.small{white-space:nowrap;max-width:240px;overflow:hidden;text-overflow:ellipsis}
@media (min-width:821px) and (max-width:1240px){ .topright>.muted.small{display:none} }
/* Check boxes big enough for a finger: the browser default is 13px, and some
   markup pins width:auto inline, hence !important (there are no hidden-input
   switches in this app for it to upset). */
input[type=checkbox],input[type=radio]{width:18px !important;height:18px !important;flex:none}
@media (max-width:820px), (max-height:500px){ input[type=checkbox],input[type=radio]{width:24px !important;height:24px !important} }
/* A folded section's title is the control: give it a finger's height. */
details>summary.small{padding-block:8px}
input[type=file]{min-height:40px}

/* THE MENU BUTTON WHEREVER THE TABS DO NOT FIT (2026-09-30).
   Measured: at 844px (a phone on its side) the one-row header showed five of
   nine tabs; the rest sat past the edge in a strip that scrolls sideways with
   nothing to say so. So the header - only the header - switches to the menu
   below 1180px or on a screen under 500px tall. The rest of the page keeps its
   own phone rules at 820px. Keep in step with HEADER_MENU_MQ in lib/view.php. */
@media (max-width:1180px), (max-height:500px){
  .topbar{height:56px;padding:0 max(10px,env(safe-area-inset-right,0px)) 0 max(10px,env(safe-area-inset-left,0px));gap:10px}
  .nav-toggle{
    display:grid;place-items:center;flex:none;width:42px;height:42px;
    background:var(--panel2);border:1px solid var(--line);border-radius:12px;
    color:var(--text);font-size:19px;line-height:1;cursor:pointer;
  }
  .brand{flex:none}
  .nav-current{display:block;flex:1 1 auto;min-width:0;color:var(--muted);font-weight:600;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .topbar>.imp,.topbar>.tabs,.topbar>.topright{display:none}
  .topbar>.theme-toggle{order:0;margin-left:0;width:42px;height:42px}
  body.nav-open{overflow:hidden}
  body.nav-open .topbar{
    position:fixed;inset:0;height:auto;z-index:400;background:var(--bg);
    -webkit-backdrop-filter:none;backdrop-filter:none;
    flex-wrap:wrap;align-content:flex-start;row-gap:14px;
    padding:7px max(10px,env(safe-area-inset-right,0px)) 28px max(10px,env(safe-area-inset-left,0px));overflow-y:auto;
  }
  body.nav-open .topbar>.tabs,body.nav-open .topbar>.topright{display:flex;flex:0 0 100%}
  body.nav-open .topbar>.imp{display:block;flex:0 0 100%}
  body.nav-open .tabs{flex-direction:column;gap:4px;overflow:visible}
  body.nav-open .tabs a{padding:12px 14px;font-size:15px;border-radius:12px;background:var(--panel2)}
  body.nav-open .tabs a.on{background:var(--accent-soft);box-shadow:inset 0 0 0 1px var(--accent-line);color:var(--accent-text,var(--text))}
  body.nav-open .topright{flex-wrap:wrap;gap:10px;margin-left:0;padding-top:12px;border-top:1px solid var(--line)}
  /* No address at the foot of the menu (owner, 2026-09-30). */
  body.nav-open .topright>.muted.small{display:none}
  body.nav-open .topright .btn{min-height:42px;padding:8px 18px}
}
/* A phone on its side has room for two columns of tabs in the open menu. */
@media (max-height:500px) and (min-width:560px){
  body.nav-open .tabs{display:grid;grid-template-columns:1fr 1fr}
}

/* Sign-in / sign-up / reset: the text links under the card are standalone
   controls ("Back to sign in", "Create one", "Back to the website"). They were
   15px tall and "Create one" broke across two lines on a phone. One piece
   each, with a finger-sized hit area that does not move the text. */
.login .small a,.login p.center a,.site-back a{display:inline-block;padding:10px 2px;margin:-8px 0;white-space:nowrap}
