/* Landing page: upload a package, watch it run, open the review.
   Colours, spacing and the square radius all come from tokens.css — nothing is hardcoded here. */
#jobsmain{flex:1;overflow:auto;scrollbar-gutter:stable both-edges;display:block;padding:24px;background:var(--bg-2)}
.panel{background:var(--bg-1);border:1px solid var(--border-1);padding:20px 22px;
       margin:0 auto 20px;max-width:860px}
.panel h2{margin:0 0 4px;font-size:15px;font-weight:600;letter-spacing:.01em}

/* panel header: the title on the left, the one thing you can do to the panel on the right */
.panelhd{display:flex;align-items:center;gap:12px;margin:0 0 14px}
.panelhd h2{margin:0}
.count{display:inline-block;min-width:20px;padding:1px 7px;background:var(--bg-3);
       color:var(--text-2);border-radius:var(--r-pill);font-size:11px;font-weight:600;
       vertical-align:2px}
.count:empty{display:none}
.ghost{font-size:12px;font-weight:600;color:var(--act-hov);text-decoration:none;
       border:1px solid var(--border-1);padding:6px 11px;cursor:pointer;white-space:nowrap}
.ghost:hover{border-color:var(--act);background:var(--t100)}

/* The create form is a disclosure now — reached for once per package, not once per visit. */
details.panel{padding:0}
details.panel>summary{list-style:none;cursor:pointer;padding:16px 22px;display:flex;
                      align-items:center;gap:12px;flex-wrap:wrap}
details.panel>summary::-webkit-details-marker{display:none}
details.panel>summary::after{content:'+';margin-left:auto;font-size:18px;color:var(--text-3);
                             line-height:1;font-weight:500}
details.panel[open]>summary::after{content:'\2212'}
details.panel>summary:hover{background:var(--bg-2)}
details.panel>summary:focus-visible{outline:2px solid var(--act2);outline-offset:-2px}
/* The body is inset by ONE wrapper. Margins on each child lost to `.panel .hint` on
   specificity, so the prose sat flush against the panel edge while the form did not. */
.panelbody{padding:0 22px 20px}

/* the wordy explanation, out of the way until it is wanted */
.legend{margin-top:14px;border-top:1px solid var(--border-2);padding-top:10px}
.legend>summary{cursor:pointer;font-size:11.5px;font-weight:600;color:var(--text-3-sm);
                letter-spacing:.02em}
.legend>summary:hover{color:var(--act-hov)}
.legend p{color:var(--text-3-sm);font-size:11.5px;line-height:1.6;max-width:70ch;margin:8px 0 0}
.panel .hint{color:var(--text-3);margin:0 0 16px;font-size:12px;line-height:1.55;max-width:62ch}
.field{display:block;margin:0 0 14px}
.field .lbl{display:block;font-size:11px;font-weight:600;text-transform:uppercase;
            letter-spacing:.06em;color:var(--text-2);margin-bottom:5px}
.field .lbl em{font-style:normal;font-weight:500;text-transform:none;letter-spacing:0;
               color:var(--text-3);margin-left:6px}
.field input[type=text]{width:100%;padding:7px 9px;border:1px solid var(--border-1);
                        border-radius:var(--r-sm);font-family:var(--ff);font-size:13px}
.field input[type=file]{width:100%;padding:9px;border:1px dashed var(--border-1);
                        background:var(--bg-2);font-family:var(--ff);font-size:12px}
.field .sub,.sub{color:var(--text-3);font-size:11.5px}
.field .sub{display:block;margin-top:5px}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.actions{display:flex;align-items:center;gap:12px;margin-top:4px}

/* ---------------------------------------------------------------- one row per job
   The row used to end in eight word-buttons, which read as eight equally important choices and
   squeezed the job's own name. Now: the name, its status, what it has done — then ONE labelled
   action, the icons for the two things reached for mid-run, and a ⋯ menu for the rest. */
.job{border:1px solid var(--border-1);padding:12px 14px;margin-bottom:10px;display:grid;
     grid-template-columns:auto minmax(0,1fr) auto;gap:14px;align-items:start;background:var(--bg-1)}
.job:hover{border-color:var(--b400)}
.jobsel, .jobsel-spacer{width:16px;height:16px;margin-top:2px}
.jobsel{cursor:pointer}

/* ---------------------------------------------------------------- bulk delete + Trash
   Same shape as a normal panel row, so checking a box doesn't visually reflow the list beneath
   it — the bar just appears above, and disappears the moment nothing is selected. */
.bulkbar{display:flex;align-items:center;gap:10px;padding:9px 14px;margin-bottom:10px;
        background:var(--bg-2);border:1px solid var(--border-2);font-size:12.5px}
.bulkbar button.danger{background:var(--bg-err);border:1px solid var(--red);color:var(--red);
        padding:5px 10px;font-size:12px;font-weight:600}
.bulkbar button.danger:hover{background:var(--red);color:#fff}
.trashrow{display:flex;align-items:center;justify-content:space-between;gap:14px;
         border:1px solid var(--border-1);padding:10px 14px;margin-bottom:8px}
.trashrow .lead{display:inline-flex;align-items:center;gap:7px;height:28px;padding:0 10px;
        font-size:12px}
.trashrow .lead .ic{width:14px;height:14px;flex:0 0 14px}
.job.busy{border-left:3px solid var(--act)}
/* Loud enough not to be mistaken for a finished run, quiet enough not to fight the palette:
   the signal is the amber edge, the pill and the bold count in the summary line. A full
   --yel-lt wash shouted but clashed with the teal the rest of the page is built from. */
.job.short{border-left:3px solid var(--warn);background:var(--bg-2)}
.job.short .name{color:var(--warn)}
.job.err{border-left:3px solid var(--red);background:var(--bg-err)}
.jmain{min-width:0}
.job .name{font-weight:600;font-size:13.5px;display:flex;align-items:center;gap:8px;
           flex-wrap:wrap;min-height:24px}
.job .meta{color:var(--text-3);font-size:11.5px;margin-top:3px;line-height:1.5}
.job .btns{display:flex;gap:6px;align-items:center;flex-wrap:wrap;justify-content:flex-end}

/* icons — 24-box paths drawn at 16, stroked in the button's own colour so hover and disabled
   need no separate icon states. `.f` marks the few shapes that are solid. */
.ic{width:16px;height:16px;flex:0 0 16px;fill:none;stroke:currentColor;stroke-width:2;
    stroke-linecap:round;stroke-linejoin:round;vertical-align:-3px}
.ic .f{fill:currentColor;stroke:none}

/* a square icon-only button */
.ib{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;
    padding:0;color:var(--text-2)}
.ib.on{border-color:var(--act);color:var(--act-hov);background:var(--t100)}

/* the row's one labelled action */
.job .lead{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 12px;
           font-weight:600;white-space:nowrap}
.job .lead .ic{width:14px;height:14px;flex:0 0 14px}

/* ⋯ menu, one per row. Same idiom as the review page's More menu. */
.menuwrap{position:relative}
.rowmenu{display:none;position:absolute;right:0;top:calc(100% + 5px);min-width:236px;
         background:var(--bg-1);border:1px solid var(--border-1);box-shadow:var(--sh-xl);
         z-index:30;padding:4px;text-align:left}
.rowmenu.on{display:block}
.rowmenu button{display:flex;align-items:center;gap:9px;width:100%;text-align:left;border:none;
                background:none;padding:8px 10px;font-size:12.5px;white-space:nowrap}
.rowmenu button:hover{background:var(--bg-2);color:var(--act-hov)}
.rowmenu button.danger{color:var(--text-2)}
.rowmenu button.danger:hover{background:var(--bg-err);color:var(--red)}
.rowmenu button:disabled:hover{background:none}

/* the title is the rename control: click it, type, Enter. The pencil only appears on hover so
   the row is not permanently decorated with an affordance nobody uses twice. */
.jobname{cursor:text;border-bottom:1px dashed transparent;display:inline-flex;
         align-items:center;gap:6px;max-width:100%}
.jobname .ic{width:13px;height:13px;flex:0 0 13px;opacity:0;color:var(--text-3)}
.jobname:hover{border-bottom-color:var(--border-1)}
.jobname:hover .ic,.jobname:focus-visible .ic{opacity:1}
.nameedit{font:600 13.5px var(--ff);padding:2px 6px;border:1px solid var(--act);
          border-radius:var(--r-sm);background:var(--bg-1);color:var(--text-1);
          min-width:min(340px,100%);box-shadow:0 0 0 3px var(--t200)}
.jobname.editing{border-bottom-color:transparent;cursor:default}
.nameedit:focus{outline:none}

/* Narrow window: the actions go under the job rather than crushing its name into one word
   per line. */
@media (max-width:700px){
  .job{grid-template-columns:minmax(0,1fr)}
  .job .btns{justify-content:flex-start}
  .rowmenu{right:auto;left:0}
}
.pill{display:inline-block;padding:2px 8px;border-radius:var(--r-pill);font-size:10.5px;
      font-weight:600;text-transform:uppercase;letter-spacing:.05em;vertical-align:1px}
.pill.idle{background:var(--bg-3);color:var(--text-3)}
.pill.running,.pill.scoring,.pill.queued,.pill.building,.pill.indexing{
  background:var(--t200);color:var(--t700)}
.pill.ready{background:var(--b300);color:var(--b900)}
.pill.done{background:var(--t500);color:var(--m100)}
/* Not done, and it must not be able to be mistaken for done: a run that stopped short has
   pages missing from its takeoff. */
.pill.incomplete{background:var(--yel-lt);color:var(--warn);box-shadow:inset 0 0 0 1px var(--warn)}
.pill.error,.pill.interrupted{background:var(--red);color:var(--m100)}
.pill.stopped{background:var(--m400);color:var(--m800)}
.bar{height:4px;background:var(--bg-3);margin-top:8px;overflow:hidden}
.bar>i{display:block;height:100%;background:var(--t600);transition:width .3s}
/* Two of the waits here have no percentage to report — the server working after the last byte
   was uploaded, and reading a workbook, which openpyxl does in one opaque call. A bar parked at
   0% or 100% for half a minute is how a hang looks, so those waits get a bar that moves without
   claiming to measure anything. */
.bar.indet>i{width:34%;transition:none;animation:indet 1.1s ease-in-out infinite}
@keyframes indet{0%{margin-left:-34%}100%{margin-left:100%}}
@media (prefers-reduced-motion:reduce){
  /* Still moving, still not a claim about progress — just no travel across the screen. */
  .bar.indet>i{width:100%;animation:indetfade 1.6s ease-in-out infinite}
  @keyframes indetfade{0%,100%{opacity:.25}50%{opacity:1}}
}
#toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);display:none;
       background:var(--d800);color:var(--m100);padding:9px 15px;font-size:12px;z-index:50}

/* live activity feed */
.log{background:var(--d900);color:var(--m300);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
     font-size:11px;line-height:1.6;padding:8px 10px;max-height:260px;overflow:auto;white-space:pre-wrap;
     word-break:break-word;
     /* Appending a line is a height change the browser would otherwise compensate for by
        scrolling the page; follow/detach here is ours to decide, not the anchor's. */
     overflow-anchor:none}
.log .ln{display:block}
.log .ln .t{color:var(--b600);margin-right:8px}
.log .ln.warn{color:var(--yel)}
.log .ln.error{color:#FF8A8A}
/* Scrolling up detaches the feed so old lines can be read while new ones arrive; this says so,
   and puts them back. Only shown while detached — see LOG.follow in jobs.js. */
.logwrap{position:relative;margin-top:10px}
.tolatest{display:none;position:absolute;right:12px;bottom:10px;align-items:center;gap:6px;
          padding:5px 9px;font-size:11px;font-weight:600;background:var(--d900);
          border-color:var(--d900);color:#fff;box-shadow:var(--sh-md)}
.tolatest.on{display:inline-flex}
.tolatest:hover{background:var(--d800);border-color:var(--d800);color:#fff}
.tolatest .ic{width:13px;height:13px;flex:0 0 13px;stroke-width:2.5}

/* a condition the user has to fix outside the app — stated up front, not on click */
.notice{background:var(--yel-lt);border:1px solid var(--border-1);border-left:3px solid var(--act);
        padding:12px 14px;margin:0 auto 20px;max-width:860px;font-size:12px;line-height:1.6}
.notice code{background:var(--m100);padding:1px 4px}
.notice pre{background:var(--d900);color:var(--m300);padding:8px 10px;margin:8px 0 0;overflow:auto;
            font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
button:disabled{opacity:.45;cursor:not-allowed}
button:disabled:hover{border-color:var(--border-1);color:var(--text-1)}
.notice.warn{background:var(--bg-2);border-left-color:var(--b600)}

/* A name the app cannot use, flagged while it is being typed rather than on submit. The name is
   the job's id, its folder and its URL, so the moment to say so is before the package is sent. */
.nameedit.bad{border-color:var(--bad-ink);box-shadow:0 0 0 3px var(--bad-bg)}
input.bad{border-color:var(--bad-ink)}
.sub.bad{color:var(--bad-ink)}

/* Client vs operator view (prefs.js; set by review_app.py --operator). */
html:not(.operator) .oponly{display:none !important}
html.operator .clientonly{display:none !important}

/* Client view: named steps and time left, in place of the raw activity feed. */
.steps{display:flex;gap:4px;margin:8px 0 4px;flex-wrap:wrap}
.steps span{font-size:11px;padding:2px 8px;border-radius:var(--r-pill);background:var(--bg-3);color:var(--text-3)}
.steps span.done{background:var(--t100);color:var(--t700)}
.steps span.now{background:var(--act);color:var(--m100);font-weight:600}
.eta{font-size:12.5px;color:var(--text-2);margin-top:2px}
.eta b{color:var(--text-1)}
.pulse{font-size:12px;color:var(--text-3);margin-top:6px;display:flex;align-items:center;gap:7px}
.pulse i{width:7px;height:7px;border-radius:0;background:var(--act);flex:0 0 7px;
  animation:beat 1.4s ease-in-out infinite}
.pulse.quiet{color:var(--warn)}
.pulse.quiet i{background:var(--warn);animation:none}
@keyframes beat{0%,100%{opacity:.25;transform:scale(.8)}50%{opacity:1;transform:scale(1.15)}}
@media (prefers-reduced-motion:reduce){.pulse i{animation:none}}
.jobname.saving{opacity:.6}

/* the assumed client convention, on every card (jobs.js conventionLine) */
.job .meta.conv select{font-size:11.5px;padding:1px 4px;max-width:340px}
.job .convbtn{background:none;border:0;padding:0 2px;color:var(--ok-ink);
  text-decoration:underline;cursor:pointer;font-size:11.5px}

/* ================================================================ layout revamp, 28 Sep 2026
   In progress on top (every busy job visible at once), All jobs as a table below, New job and
   Trash as side drawers. Same visual language as the review page (app.css). */
#jobsmain{padding:20px 24px}
.panel.wide,.notice{max-width:1400px}
.summary{display:flex;gap:28px;flex-wrap:wrap;max-width:1400px;margin:0 auto 16px}
.summary div{display:flex;flex-direction:column}
.summary small{font-size:10.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--text-3-sm);font-weight:600}
.summary strong{font-size:22px;font-weight:600;font-variant-numeric:tabular-nums}
#livegrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr));gap:12px}
#livegrid>.hint{margin:0}
.livecard{border:1px solid var(--border-1);border-left:3px solid var(--act);padding:12px 14px;min-width:0;
  display:flex;flex-direction:column;gap:4px}
.livecard .name{font-weight:600;font-size:13.5px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.livecard .meta{color:var(--text-3);font-size:11.5px;line-height:1.5}
.livecard .btns{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;align-items:center}
.livecard .lead{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 12px;font-weight:600;white-space:nowrap}
.livecard .lead .ic{width:14px;height:14px;flex:0 0 14px}
.livecard .log{max-height:180px}
#listpanel .panelhd input[type=search]{border:1px solid var(--border-1);padding:6px 9px;font:inherit;width:220px;max-width:100%}
.chips{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 12px}
.chip{padding:5px 11px;border:1px solid var(--d400);font-size:11px;background:var(--bg-1);cursor:pointer}
.chip.on{background:var(--d900);border-color:var(--d900);color:#fff;font-weight:600}
.chip .of{color:var(--text-3-sm)} .chip.on .of{color:var(--b400)}
.tablewrap{overflow:visible}
@media (max-width:900px){.tablewrap{overflow-x:auto}}
.jobtable{width:100%;border-collapse:collapse;min-width:760px}
.jobtable th{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-2);font-weight:600;
  text-align:left;padding:7px 10px;background:var(--d100);border:1px solid var(--border-1);white-space:nowrap;user-select:none}
.jobtable th[data-sort]{cursor:pointer}
.jobtable th.asc::after{content:' ↑'} .jobtable th.desc::after{content:' ↓'}
.jobtable td{padding:9px 10px;border:1px solid var(--border-1);vertical-align:top;font-size:12.5px}
.jobtable .r{text-align:right}
.jobtable .num{font-variant-numeric:tabular-nums;white-space:nowrap}
/* Result column, no manual takeoff: derived-confidence roll-up */
.jobtable .confflag{color:var(--warn);font-weight:700;text-decoration:none;border-bottom:1px dotted currentColor}
.jobtable .confok{color:var(--ok-ink)}
.jobtable .ck{width:34px}
.jobtable tr.job{display:table-row;margin:0;padding:0;border:0}
.jobtable tr.job:hover td{background:var(--bg-2)}
.jobtable tr.job.busy td:first-child{box-shadow:inset 3px 0 0 var(--act)}
.jobtable tr.job.short td:first-child{box-shadow:inset 3px 0 0 var(--warn)}
.jobtable tr.job.err td{background:var(--bg-err)}
.jobtable .btns{justify-content:flex-end;flex-wrap:nowrap}
.mini{display:inline-block;width:56px;height:4px;background:var(--bg-3);vertical-align:middle;margin-right:6px}
.mini>i{display:block;height:100%;background:var(--t600)}
.scrim{position:fixed;inset:0;background:rgba(27,31,36,.3);z-index:40}
.drawer{position:fixed;top:0;right:0;bottom:0;width:min(560px,100%);background:var(--bg-1);z-index:41;
  border-left:1px solid var(--border-1);box-shadow:var(--sh-xl);overflow:auto}
.drawer[hidden],.scrim[hidden]{display:none}
.drawer .dhead{display:flex;align-items:flex-start;gap:12px;padding:16px 22px;border-bottom:1px solid var(--border-1);
  position:sticky;top:0;background:var(--bg-1);z-index:1}
.drawer .dhead h2{margin:0 0 2px;font-size:16px}
.drawer .x{margin-left:auto;border:0;background:none;font-size:22px;line-height:1;padding:0 4px;color:var(--text-3)}
.drawer .panelbody{padding:16px 22px 22px}
@media (max-width:700px){.drawer .row2{grid-template-columns:1fr}.summary{gap:18px}}
.alerttile{display:flex;flex-direction:column;text-decoration:none;color:inherit}
.alerttile:hover strong{text-decoration:underline}
.alerttile strong.hot{color:var(--warn)}
.alertrow{padding:8px 10px;border:1px solid var(--border-1);border-left:3px solid var(--warn);margin-bottom:6px;font-size:12.5px}
.alertrow.bad{border-left-color:var(--red)}
.alertrow code{font-size:11px;background:var(--bg-2);padding:1px 5px;margin-right:4px}
.alertkey{display:grid;grid-template-columns:130px 1fr;gap:5px 12px;font-size:11.5px;color:var(--text-2);margin-top:18px;border-top:1px solid var(--border-2);padding-top:12px}
.alertkey dt{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}
.alertkey dd{margin:0}
.jobtable.diag{min-width:640px}
.jobtable.diag td{padding:5px 8px}
/* summary counts: hover highlights the jobs they count, click filters the list to them */
.sumtile{display:flex;flex-direction:column;align-items:flex-start;border:1px solid transparent;
  background:none;padding:4px 10px;margin:-4px -10px;cursor:pointer;text-align:left;font:inherit;color:inherit}
.sumtile:hover,.sumtile:focus-visible{border-color:var(--d400);background:var(--bg-1)}
.sumtile.on{border-color:var(--d900);background:var(--bg-1)}
.jobtable tr.job.gone{display:none}
.chip.preview{border-color:var(--d900)}
.livecard:not(.busy){border-left-color:var(--d400)}
.livecard.short{border-left-color:var(--warn)} .livecard.err{border-left-color:var(--red);background:var(--bg-err)}
.livecard .ghostbtn{color:var(--text-2)}
.livecard .ghostbtn:hover{border-color:var(--red);color:var(--red);background:var(--bg-err)}
/* the manual takeoff: onboarding and calibration only, so out of the way unless wanted */
details.calib{border-top:1px solid var(--border-2);padding-top:10px;margin:4px 0 14px}
details.calib>summary{cursor:pointer;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-2)}
details.calib>summary em{font-style:normal;font-weight:500;text-transform:none;letter-spacing:0;color:var(--text-3);margin-left:6px}
details.calib[open]>summary{margin-bottom:8px}
details.calib>.sub{display:block;margin:0 0 10px}
.jobtable.nomatch th[data-sort="match"],.jobtable.nomatch tr.job>td:nth-child(6){display:none}
/* Match is secondary now: greyed, and off unless the viewer turns it on (remembered per browser). */
.jobtable .mcol,.jobtable tr.job>td.mcol{color:var(--text-3-sm)}
.jobtable:not(.showmatch) th.mcol,.jobtable:not(.showmatch) tr.job>td.mcol{display:none}
.mtoggle{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:var(--text-2);cursor:pointer;white-space:nowrap}
.jobtable .conflow{color:var(--warn);font-weight:700}
