  :root{
    --ink:#212423; --ink-2:#33403a; --panel:#fcf8f3; --line:#e7dcc4;
    --paper:#ffffff; --muted:#6c7169; --soft:#a2a294;
    --accent:#1f6f4f; --accent-soft:#e4f0ea; --accent-line:#a8d3c1;
    --brand:#004226; --brand-2:#00603a;
    /* Item 213 - the parts band and the strip under it, both read straight
       off Andy's design rather than guessed at: #29825a and #e3dbc7. The
       active part is cut out of the band in the strip's own colour, so the
       two read as one surface. */
    --brand-3:#29825a; --strip:#e3dbc7;
    --ok:#2f6b4f; --ok-soft:#e6f0ea; --warn:#9a5b00; --warn-soft:#f8ecd6;
    --stop:#b50000; --stop-soft:#f7dede;
    /* Item 237 - one colour for every count-on-a-thing badge, so the
       one on your initials and the one on a tool in the menu are the
       same thing said twice rather than two different alarms. Andy:
       *"they should be the same. Maybe the orange is best so not so
       drastic looking"*. */
    --badge:#c0452f;
    --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
    --sans:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
  }
  *{box-sizing:border-box}
  body{margin:0;font-family:var(--sans);color:var(--ink);background:#efe9da;font-size:14px;line-height:1.5}
  header.top{background:linear-gradient(105deg,var(--brand),var(--brand-2) 72%,#006b3c);color:#f4f1ea;padding:15px 26px;display:flex;align-items:center;gap:18px;flex-wrap:wrap}
  /* The green bar stays at the top as the page scrolls, as it does on the
     Import Builder (Andy, 11 September), so the menu, Help, what is running
     and the account menu are always in reach. Not on phones or short
     windows, where the bar wraps to several lines and would take a quarter
     of the screen. --hdr-h is the bar's height (set by MOTION_JS), for
     anything else that sticks, and for jumps to a #link. */
  @media (min-width:760px) and (min-height:560px){
    /* Item 233. The local-copy bar stays put too, above the header. It used
       to scroll away, which was a deliberate choice and a wrong one: Andy,
       looking at a scrolled local page, *"the orange bar goes off the
       screen - which again could lead to confusion"*. A warning you can
       scroll past is a warning that is absent exactly when somebody is deep
       in a screen and has forgotten which copy they are on.
       --copy-h is its height (MOTION_JS), 0px on the live copy, which has
       no bar - so every sum below is unchanged there. */
    .copybar{position:sticky;top:0;z-index:46}
    header.top{position:sticky;top:var(--copy-h,0px);z-index:45;
      box-shadow:0 2px 12px rgba(0,32,18,.16)}
    /* Item 213 - the parts band sticks under the header, so the way between
       the parts is always in reach. --chrome-h is both bars together, which
       is what anything sticking below them has to clear. */
    /* --hdr-h already counts the copy bar above it, so this is the header's
       bottom edge, not the header's own height. */
    .partsbar{position:sticky;top:var(--hdr-h,0px);z-index:44}
    html{scroll-padding-top:calc(var(--chrome-h,var(--hdr-h,0px)) + 12px)}
    .stick-below{top:calc(var(--chrome-h,var(--hdr-h,0px)) + 14px) !important}
    /* Column headings stay under the green bar as the table scrolls past.
       Only where the table fits the window: a table wide enough to scroll
       sideways sits in a scrolling box of its own, and nothing inside one of
       those can stick to the window. MOTION_JS adds .fits when it fits. */
    .table-wrap.fits{overflow:visible}
    .table-wrap.fits thead{position:sticky;
      top:var(--chrome-h,var(--hdr-h,0px));z-index:3}
    .table-wrap.fits thead th{box-shadow:inset 0 -1px 0 var(--line),0 1px 0 var(--line)}
  }
  /* Item 213 - an icon-only link in the green header, beside the page's
     name. Light enough to read on the dark green (Andy: *"needs to be light
     to be easier to see"*), and given room on its left so it does not touch
     the badge it sits next to. */
  header.top .hdricon{display:inline-flex;align-items:center;
    color:rgba(244,241,234,.92);margin-left:12px;vertical-align:middle;
    padding:2px}
  header.top .hdricon:hover{color:#fff}
  header.top .logo{display:flex;align-items:center;color:#fff}
  header.top .logo svg{height:25px;width:auto;display:block}
  header.top .divider{width:1px;height:26px;background:rgba(255,255,255,.22)}
  header.top .titles{display:flex;flex-direction:column;gap:2px}
  header.top h1{margin:0;font-size:15px;font-weight:600;letter-spacing:.02em}
  /* Which tool this page belongs to, before the page's own name. */
  header.top h1 .crumb{color:#bcd7c9;font-weight:500}
  header.top h1 .crumb::after{content:' / ';color:rgba(255,255,255,.45)}
  header.top .sub{font-family:var(--mono);font-size:10.5px;color:#a9c6b8;letter-spacing:.05em;text-transform:uppercase}
  header.top .spacer{flex:1}
  header.top .ver{font-family:var(--mono);font-size:11px;font-weight:400;color:#fff;background:rgba(255,255,255,.16);border:1px solid rgba(255,255,255,.25);padding:1px 8px;border-radius:20px;letter-spacing:.04em;vertical-align:middle;margin-left:8px}
  /* Item 233 - on a copy that is not the live one, the version roundel
     wears the bar's colour. A second signal, in the header, which sticks
     whatever else scrolls and survives any later change to the bar. Andy
     asked for one or the other; both is cheap and they fail differently. */
  body.notlive header.top .ver{background:#c0452f;border-color:#e08a78;color:#fff}
  header.top .helpbtn{font-family:var(--mono);font-size:11px;color:#fff;background:rgba(255,255,255,.16);border:1px solid rgba(255,255,255,.25);padding:4px 12px;border-radius:20px;letter-spacing:.03em;cursor:pointer}
  header.top .helpbtn:hover{background:rgba(255,255,255,.28)}
  .peekimgs{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:10px;padding:11px;max-height:40vh;overflow:auto}
  .peekimgs figure{margin:0;border:1px solid var(--line);border-radius:6px;overflow:hidden;background:#fafbfa}
  .peekimgs img{display:block;width:100%;height:96px;object-fit:contain;background:#fff}
  .peekimgs figcaption{font-size:9.5px;color:var(--muted);padding:4px 5px;border-top:1px solid var(--line);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .peekimgs figure.dead{border-color:#d9534f}
  .peekimgs figure.dead img{display:none}
  .peekimgs figure.dead figcaption{color:#d9534f}
  .peekimgs figure.dead::before{content:'image did not load';display:block;height:96px;line-height:96px;text-align:center;font-size:10px;color:#d9534f}
  .peekbox{border:1px solid var(--line);border-radius:8px;overflow:hidden}
  .peekbox.second{margin-top:12px}
  .peekhead{display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:7px 11px;background:var(--wash,#f6f7f5);border-bottom:1px solid var(--line);font-size:12px}
  .peektext{padding:11px;font-size:12.5px;line-height:1.6;white-space:pre-wrap;word-break:break-word;max-height:34vh;overflow:auto}
  .peek{background:none;border:1px solid var(--line);border-radius:5px;color:var(--muted);padding:2px 5px;cursor:pointer;vertical-align:middle;line-height:1;margin-left:2px}
  .peek:hover{border-color:var(--accent);color:var(--accent)}
  .peek:disabled{opacity:.35;cursor:default}
  a.tag, a.tag:visited{color:var(--accent);text-decoration:underline;text-underline-offset:2px}
  a.tag:hover{color:var(--accent-dark,var(--accent));}
  /* Zero-JS image comparison overlay: a plain #anchor + :target, same trick used nowhere else in this app yet but no different in kind from the <details> toggles already relied on elsewhere - the browser does the showing/hiding, not a script. */
  .imgcompare{display:none;position:fixed;inset:0;z-index:80}
  .imgcompare:target{display:block}
  .imgcompare-bg{position:absolute;inset:0;background:rgba(20,26,22,.7)}
  .imgcompare-panel{position:relative;max-width:1200px;width:92vw;margin:4vh auto;background:#fff;border-radius:12px;padding:28px;display:flex;gap:26px;justify-content:center;flex-wrap:wrap;box-shadow:0 10px 40px rgba(0,0,0,.25)}
  .imgcompare-col{flex:1;min-width:280px;max-width:540px;text-align:center}
  .imgcompare-col img{max-width:100%;max-height:78vh;object-fit:contain;border:1px solid var(--line);border-radius:8px;background:#fff}
  .imgcompare-col .tag{margin-bottom:8px;display:inline-block}
  .imgcompare-close{position:absolute;top:10px;right:14px;font-family:var(--mono);font-size:12px;color:var(--muted);text-decoration:none;padding:4px 9px;border:1px solid var(--line);border-radius:20px;background:#fff}
  .imgcompare-close:hover{border-color:var(--accent);color:var(--accent)}
  .peek:disabled:hover{border-color:var(--line);color:var(--muted)}
  /* inside a field label: no border or padding, so the label keeps its normal height
     and every dropdown in the row still lines up. The label is uppercase, so the icon is
     sized to the capitals and lifted 1px to sit on their optical centre rather than the
     centre of the whole line box, which includes descender space the capitals never use. */
  label.fld .peek{border:none;padding:0;margin:0 0 0 6px;height:12px;display:inline-flex;align-items:center;transform:translateY(-1px)}
  label.fld .peek svg{display:block;width:12px;height:12px}
  label.fld{display:flex;align-items:center;min-height:14px}
.mergecols{display:flex;flex-direction:column;gap:2px;max-height:190px;overflow:auto;border:1px solid var(--line);border-radius:8px;padding:8px;margin-top:5px}
  .mrow{display:flex;align-items:center;gap:8px;font-size:12.5px;padding:2px 0;cursor:pointer}
  .mrow input{margin:0}
  .guide h4{margin:0 0 6px;font-size:13.5px;color:var(--ink)}
  .guide p{margin:0 0 8px;font-size:12.5px;line-height:1.6;color:var(--ink-2)}
  .guide ul{margin:0 0 8px;padding-left:18px;font-size:12.5px;line-height:1.6;color:var(--ink-2)}
  .guide li{margin:0 0 4px}
  .guide details summary{color:var(--accent);font-weight:600}
  .guide .intro{font-size:12.5px;line-height:1.6;color:var(--muted);margin:0 0 14px}
  .wrap{max-width:1180px;margin:0 auto;padding:22px}
  /* stepper */
  .steps{display:flex;gap:0;margin:0 0 20px;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:var(--paper)}
  .steps .st{flex:1;padding:11px 14px;font-size:12px;font-family:var(--mono);letter-spacing:.03em;color:var(--soft);border-right:1px solid var(--line);display:flex;align-items:center;gap:9px}
  .steps .st:last-child{border-right:0}
  .steps .st .n{width:20px;height:20px;border-radius:50%;border:1px solid var(--line);display:flex;align-items:center;justify-content:center;font-size:11px;color:var(--soft)}
  .steps .st.active{color:var(--ink);background:var(--accent-soft)}
  .steps .st.active .n{background:var(--accent);color:#fff;border-color:var(--accent)}
  .steps .st.done{color:var(--ink)}
  .steps .st.done .n{background:var(--ok);color:#fff;border-color:var(--ok)}
  .card{background:var(--paper);border:1px solid var(--line);border-radius:8px;padding:22px;margin-bottom:18px}
  .card h2{margin:0 0 4px;font-size:15px}
  .card .lead{color:var(--muted);margin:0 0 18px;font-size:13px}
  /* dropzone */
  .drop{border:1.5px dashed var(--accent-line);border-radius:8px;background:var(--accent-soft);padding:40px;text-align:center;cursor:pointer;transition:.15s}
  .drop:hover{background:#dcebe3}
  .drop strong{display:block;font-size:15px;margin-bottom:5px}
  .drop span{color:var(--muted);font-size:12.5px}
  .drop.busy{opacity:.6;pointer-events:none}
  button,a.ghost{font-family:var(--sans);font-size:13px;border-radius:6px;border:1px solid var(--ink);background:var(--ink);color:#fff;padding:9px 16px;cursor:pointer;font-weight:500;text-decoration:none;display:inline-block}
  button:hover{background:var(--ink-2)}
  button.ghost,a.ghost{background:var(--panel);color:var(--ink);border-color:var(--line)}
  button.ghost:hover,a.ghost:hover{background:var(--accent-soft);border-color:var(--accent-line)}
  /* item 49: a.ghost is a real link, not a button - the global
     a:visited/a:hover rules below (element+pseudo-class, same
     specificity as a.ghost's own element+class selector) would win
     any tie by sitting later in this file - the exact trap items
     13/33 hit on .badge/.amznbadge. A class-only override (no "a")
     outranks both regardless of source order. */
  .ghost:visited{color:var(--ink)}
  .ghost:hover{text-decoration:none}
  button.accent{background:var(--accent);border-color:var(--accent)}
  button.accent:hover{filter:brightness(1.06)}
  button:disabled{opacity:.4;cursor:not-allowed}
  button.sm,a.sm{padding:7px 12px;font-size:12px}
  .row{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
  .right{margin-left:auto}
  label.fld{display:block;font-size:11px;font-family:var(--mono);text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin:0 0 5px}
  select,input[type=text],input[type=number],input:not([type]){font-family:var(--sans);font-size:13px;padding:8px 9px;border:1px solid var(--line);border-radius:6px;background:#fff;width:100%}
  select:focus,input:focus{outline:2px solid var(--accent-line);outline-offset:-1px}
  .grid2{display:grid;grid-template-columns:repeat(2,1fr);gap:14px}
  .grid3{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
  .grid4{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
  .fieldset{border:1px solid var(--line);border-radius:8px;padding:16px;margin-bottom:16px}
  .fieldset.spaced{margin-top:26px}
  .fexplain{color:var(--muted);font-size:12.5px;line-height:1.55;margin:0 0 12px}
  .fieldset > .ftitle{font-size:12px;font-family:var(--mono);text-transform:uppercase;letter-spacing:.05em;color:var(--accent);margin:0 0 13px;font-weight:600}
  table{border-collapse:collapse;width:100%;font-size:12.5px}
  th,td{text-align:left;padding:7px 9px;border-bottom:1px solid var(--line);vertical-align:top}
  th{font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:600}
  thead th{background:var(--panel)}
  tbody td{background:var(--paper)}
  td.mono,.mono{font-family:var(--mono);font-size:12px}
  .pill{display:inline-block;font-family:var(--mono);font-size:10.5px;padding:2px 7px;border-radius:20px;letter-spacing:.03em}
  .pill.opt{background:var(--accent-soft);color:var(--accent)}
  .pill.app{background:var(--panel);color:var(--muted);border:1px solid var(--line)}
  .pill.skip{background:#eee;color:var(--soft);text-decoration:line-through}
  .pill.role{background:#eef0d8;color:#5c6027}
  .preview{overflow:auto;border:1px solid var(--line);border-radius:6px;max-height:240px}
  .preview th.fromsec{color:var(--accent)}
  .preview .secdot{font-size:9px;border:1px solid var(--accent);border-radius:8px;padding:0 4px;margin-left:3px;vertical-align:middle;font-weight:400}
  .preview table{font-size:11.5px}
  .preview td{white-space:nowrap;max-width:260px;overflow:hidden;text-overflow:ellipsis}
  table.outtbl th{position:sticky;top:0;background:var(--brand);color:#f4f1ea;z-index:2;white-space:nowrap}
  table.outtbl td{max-width:220px}
  table.outtbl td.empty{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 4px,#f3f3f3 4px,#f3f3f3 8px)}
  .bucketbar{display:inline-flex;border:1px solid var(--line);border-radius:6px;overflow:hidden}
  .bucketbar button{border:0;border-radius:0;background:#fff;color:var(--muted);padding:4px 10px;font-size:11.5px;font-family:var(--mono)}
  .bucketbar button.on.opt{background:var(--accent);color:#fff}
  .bucketbar button.on.app{background:var(--ink);color:#fff}
  .bucketbar button.on.bullet{background:#3a6b53;color:#fff}
  .bucketbar button.on.skip{background:#777;color:#fff}
  .bucketbar button+button{border-left:1px solid var(--line)}
  .note{font-size:12px;padding:11px 13px;border-radius:6px;margin:0 0 14px}
  .note.info{background:#e9f1ec;color:#236b4d}
  .note.warn{background:var(--warn-soft);color:var(--warn)}
  .note.ok{background:var(--ok-soft);color:var(--ok)}
  .stat{display:flex;gap:22px;flex-wrap:wrap;margin:0 0 18px}
  .statrow{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px}
  .statrow .stat{margin:0}
  /* Every figure is the same box whether or not it is a link, so the
     labels line up (Andy, 13 September), and a long label wraps to a second
     line rather than stretching the row. */
  .stat{align-items:flex-start}
  .stat .s{min-width:96px;max-width:136px;box-sizing:border-box;padding:4px 8px;
    border:1px solid transparent;border-radius:10px;margin:0 -8px}
  .stat .s .k{white-space:normal}
  a.s.go{display:block;color:inherit;text-decoration:none}
  a.s.go:hover{background:var(--accent-soft);border-color:var(--accent-line);text-decoration:none}
  a.s.go:hover .v,a.s.go:hover .k{color:var(--accent)}
  a.s.go.on{background:var(--accent-soft);border-color:var(--accent-line)}
  a.s.go.on .v,a.s.go.on .k{color:var(--accent)}
  .stat .s .v{font-size:26px;font-weight:600;font-family:var(--mono)}
  .stat .s .k{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;font-family:var(--mono)}
  .stat .s .k{line-height:1.45}
  /* Item 232 - the second line under a figure's label: how much of the job
     is NOT done. Smaller and quieter than the label, because it qualifies
     the number rather than naming it, and on its own line so the eye reads
     "checked" first and the caveat second. */
  .stat .s .k .substat{display:block;text-transform:none;letter-spacing:0;
    font-size:10.5px;opacity:.8;margin-top:1px}
  .flagrow{border-left:3px solid var(--warn);padding:8px 12px;background:var(--warn-soft);border-radius:0 6px 6px 0;margin-bottom:7px;font-size:12.5px}
  .flagrow.stop{border-color:var(--stop);background:var(--stop-soft)}
  .fdetail{margin-top:6px;padding-top:6px;border-top:1px dashed rgba(0,0,0,.12);font-size:11.5px;line-height:1.55}
  .fdetail .mono{font-size:11px}
  .hidden{display:none}
  .muted{color:var(--muted)}
  .tag{font-family:var(--mono);font-size:11px;color:var(--muted)}
  code.k{font-family:var(--mono);background:var(--panel);border:1px solid var(--line);padding:1px 5px;border-radius:4px;font-size:11.5px}
  .scrollx{overflow-x:auto}
  .dvals{display:flex;gap:5px;flex-wrap:wrap;align-items:center;max-width:340px}
  .dval{font-family:var(--mono);font-size:11px;background:var(--panel);border:1px solid var(--line);padding:1px 6px;border-radius:4px;color:var(--ink)}
  .dcount{font-family:var(--mono);font-size:10px;color:var(--accent);background:var(--accent-soft);padding:1px 6px;border-radius:20px;white-space:nowrap}
  .dcount.one{color:var(--soft);background:#eee}
  .rowbadge{font-family:var(--mono);font-size:12px;font-weight:400;color:var(--accent);background:var(--accent-soft);padding:2px 9px;border-radius:20px;margin-left:8px;vertical-align:middle}
  /* Item 29: icon-only Amazon-link badge, replacing a text "Amazon" pill
     that was costing real width on the sellers list. A plain arrow on a
     neutral orange tint - not Amazon's own logo shape or exact brand
     orange, same reasoning as item 12's favicon decision.
     Item 33: switched from a circle/tinted-background+dark-text combo to
     a solid orange rounded square with a white arrow - Andy reported the
     icon looking like different colours across rows, root-caused to the
     browser's own a:visited style (0,1,1 specificity) beating the plain
     .amznbadge class rule (0,1,0), the exact same trap item 13's
     .badge:visited note describes - a clicked/visited badge showed the
     browser default visited colour bleeding through instead of the
     intended tint. Explicit :visited/:hover overrides below close that
     gap for good, on top of the new solid-orange design Andy asked for. */
  .amznbadge{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:6px;background:#e2861f;color:#fff;font-size:12px;margin-left:6px;vertical-align:middle;text-decoration:none}
  /* Item 71: the same badge, but wide enough to hold a label. Only the
     fixed square is relaxed - the orange, the radius, the flex centring
     and the text-decoration/:visited/:hover guards above are all
     inherited, so the labelled and icon-only forms cannot drift apart.
     Used on the seller page header, the one place with room for it: the
     sellers list is icon-only by choice (what item 29 traded a text pill
     away for), and the 420px Settings card wraps the second link onto its
     own line. */
  .amznbadge.wide{width:auto;padding:0 9px;gap:5px;font-size:11px;
    font-family:var(--mono);letter-spacing:.02em}
  .amznbadge:visited,.amznbadge:hover,.amznbadge:active{color:#fff}
  table a.amznbadge{text-decoration:none}
  /* Item 29: numeric sellers-list cells often pair a count with a small
     parenthetical share ("40 (80%)") - the space between them is a
     natural wrap point once the column gets squeezed, so pin these to one
     line and let .table-wrap's existing horizontal scroll take over
     instead of an ugly mid-cell break. Headers are unaffected (th.num is
     not included), so a narrow=True sort_link() header can still wrap. */
  td.num{white-space:nowrap}
  .filebar{font-family:var(--mono);font-size:12px;color:var(--muted);margin:2px 0 16px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
  .filebar .fname{color:var(--ink);background:var(--panel);border:1px solid var(--line);padding:2px 9px;border-radius:5px}
  .filebar .prof{color:var(--accent)}
  /* Catalogue cleanup: one row per duplicate group. A card each was
     unreadable at 1,555 groups, so the row carries the decision and the
     evidence sits behind a toggle. */
  .pickcat .pickrow{display:flex;align-items:center;gap:8px;
    margin-top:4px;flex-wrap:wrap}
  .pickcat .pickname{color:var(--ink);font-size:12.5px}
  .pickcat{margin:6px 0;padding:8px 11px;border:1px solid var(--warn);
    border-radius:6px;background:var(--panel);font-size:12.5px}
  .pickcat .row{flex-wrap:wrap;gap:4px 0;align-items:center}
  /* Only what is new here: the layout, the search box and the select all
     come from .filterbar, which is what every other search on the site
     uses. */
  .pickbar{margin:12px 0 0}
  /* Item 197 - the run history's kind picker. Twenty kinds is a list, not a
     row of tabs. */
  .pickbar .runpick{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
  /* width:auto beats the global `select{width:100%}` on line 212, which
     otherwise stretches the picker across the whole page. */
  .pickbar .runpick select{padding:8px 12px;border-radius:999px;
    border:1px solid var(--line);background:#fff;font-size:13px;
    font-family:var(--sans);width:auto;flex:0 1 auto;max-width:100%}
  .pickbar .brandpick{display:flex;align-items:center;gap:8px;
    flex-wrap:nowrap}
  /* Three classes deep on purpose: .filterbar input[type=search] sets
     width:100%, and a bare .brandfilter rule loses to it and stretches the
     box across the bar. */
  .pickbar .brandpick .brandfilter{width:150px;flex:0 0 150px;
    padding:9px 14px;border-radius:999px;border:1px solid var(--line);
    background:#fff;font-size:13px;font-family:var(--sans);
    -webkit-appearance:none}
  .pickbar .brandpick .brandfilter:focus{outline:2px solid var(--accent-line);
    outline-offset:-1px;border-color:var(--accent-line)}
  .pickbar .brandpick select{flex:0 1 300px;max-width:300px}
  /* The product search should not eat the whole bar either. Both classes on
     the same element, to beat .filterbar .searchwrap's flex:1 1 280px, which
     otherwise wins on source order. */
  .pickbar.filterbar .searchwrap{flex:0 1 330px;min-width:220px}
  .pickbar .brandcount{font-size:11.5px;white-space:nowrap}
  .pickbar .pickhint{flex:1 1 100%;font-size:11.5px;color:var(--muted)}
  .exportbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
    margin:12px 0 4px;padding:9px 13px;border:1px solid var(--accent);
    border-radius:6px;background:var(--panel);font-size:12.5px}
  .exportbar b{color:var(--accent)}
  .exportbar .right{margin-left:auto}
  .filebar .right{margin-left:auto}
  .filebar form.right{margin-left:auto;margin-right:14px}
  .bulkbar{margin:10px 0;align-items:center;gap:8px}
  .bulkbar .ticked{display:flex;align-items:center;gap:8px}
  .bulkbar .tickall{display:inline-flex;align-items:center;gap:5px;
    font-size:12px;color:var(--muted);white-space:nowrap;cursor:pointer}
  .bulkhelp{font-size:12px;line-height:1.55;margin:-2px 0 12px;width:100%}
  .filterchip{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;
    margin:12px 0 0;padding:8px 12px;border:1px solid var(--warn);
    border-radius:6px;background:var(--panel);font-size:12.5px}
  .filterchip .what{font-weight:600;color:var(--ink)}
  .filterchip .why{color:var(--muted);font-size:12px;flex:1 1 240px}
  .filterchip .clear{font-weight:600;white-space:nowrap}
  .cleanrow{border:1px solid var(--line);border-radius:8px;padding:9px 12px;
    margin:0 0 7px;background:#fff}
  .cleanrow .cleanhead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
  .cleanrow .cname{font-weight:600;font-size:13.5px;color:var(--ink)}
  .cleanrow .cleanact{font-size:12.5px;color:var(--ink);margin:4px 0 5px}
  .cleanrow input.pick{margin:0 2px 0 0}
  /* A warning pill. NOT .pill.skip, which carries text-decoration:
     line-through - item 68's trap, where a struck-through label read as
     "cancelled" rather than "look at this". */
  .pill.risk{background:var(--warn-soft,#fbeedd);color:var(--warn,#9a5b00);
    border:1px solid var(--warn,#9a5b00)}
  .cleanrow details{margin-top:2px}
  .cleanrow > details.mini > summary{position:absolute;top:8px;right:12px;
    margin:0}
  .cleanrow .cleanact{padding-right:70px;margin:2px 0 0}
  .groupstats{margin-top:14px;padding:10px 13px;background:var(--panel);border:1px solid var(--line);border-radius:6px;font-size:12.5px;font-family:var(--mono);color:var(--ink);display:flex;gap:18px;flex-wrap:wrap}
  .groupstats b{color:var(--accent);font-weight:600}
  .flagrow a{color:var(--accent);text-decoration:underline;text-underline-offset:2px}
  .flagrow a:hover{color:var(--brand)}
  .ghost.mini{padding:4px 11px;font-size:12px;white-space:nowrap}
  .descpane{border:1px solid var(--line);border-radius:8px;padding:10px;font-size:12px;line-height:1.55;white-space:pre-wrap;background:#fff}
  .descpane .ins{background:#d6efdc}
  .descpane .del{background:#f7dada;text-decoration:line-through}
  .descedit{width:100%;min-height:120px;max-height:220px;overflow:auto;font-size:12.5px;padding:8px;border:1px solid var(--line);border-radius:8px;background:#fff;line-height:1.55;white-space:pre-wrap}
  .descedit .vary{background:#fff2c2;border-radius:3px}
  .flagrow .warn{color:var(--warn);font-size:11.5px}
  .imgblock{border:1px solid var(--line);border-radius:10px;padding:16px;margin:0 0 14px;background:var(--panel)}
  .secttl{font-weight:600;font-size:15.5px;color:var(--ink);margin:18px 0 8px}
  .rsec{border:1px solid var(--line);border-radius:10px;margin:0 0 14px;background:var(--panel);overflow:hidden}
  .rsec-head{display:flex;align-items:center;gap:9px;padding:13px 15px;cursor:pointer;user-select:none}
  .rsec-head .chev{color:var(--soft);font-size:12px;transition:transform .15s;width:10px;display:inline-block}
  .rsec.collapsed .chev{transform:rotate(-90deg)}
  .rsec.collapsed .rsec-body{display:none}
  .rsec-title{font-weight:600;font-size:15.5px;color:var(--ink)}
  .rsec-count{color:var(--soft);font-size:12.5px}
  .rsec-actions{margin-left:auto;display:flex;gap:6px}
  .rsec-body{padding:0 15px 15px}
  footer a{color:var(--accent);text-decoration:none}
  footer a:hover{text-decoration:underline}
  .cl-entry{padding:11px 0;border-bottom:1px solid var(--line)}
  .cl-entry:last-child{border-bottom:none}
  .cl-ver{font-family:var(--mono);font-size:12px;font-weight:600;color:var(--accent);display:inline-block;margin-bottom:3px}
  .cl-ver.current{background:var(--accent);color:#fff;padding:1px 7px;border-radius:20px}
  .cl-entry p{margin:2px 0 0;font-size:13px;color:var(--ink);line-height:1.5}
  .cl-list{margin:6px 0 0;padding-left:20px}
  .cl-list li{margin:0 0 9px;font-size:13px;line-height:1.5;color:var(--ink)}
  .cl-list li:last-child{margin-bottom:0}
  .cl-list li b{display:block;color:var(--ink);margin-bottom:1px}
  .cl-list li span{color:var(--ink-2)}
  .cl-list li::marker{color:var(--muted);font-family:var(--mono);font-size:11px}
  .modal-overlay{position:fixed;inset:0;background:rgba(0,32,18,.42);display:none;align-items:center;justify-content:center;z-index:50;padding:20px}
  .modal-overlay.show{display:flex}
  .modal{position:relative;background:var(--paper);border:1px solid var(--line);border-radius:14px;box-shadow:0 18px 50px rgba(0,32,18,.25);max-width:440px;width:100%;padding:26px 26px 22px}
  .modal h3{margin:0 0 8px;font-size:17px;color:var(--ink)}
  .modal p{margin:0 0 18px;font-size:13.5px;color:var(--muted);line-height:1.55}
  .modal-actions{display:flex;flex-direction:column;gap:9px}
  .modal-actions button{width:100%}
  .modal-x{position:absolute;top:12px;right:14px;background:none;border:none;font-size:22px;line-height:1;color:var(--soft);cursor:pointer;padding:2px 6px;border-radius:6px}
  .modal-x:hover{color:var(--ink);background:var(--panel)}
  .groupstats .warn{color:var(--warn)}
  details{border:1px solid var(--line);border-radius:6px;padding:0 12px;margin-bottom:10px}
  details summary{cursor:pointer;padding:10px 0;font-size:12.5px;font-weight:500}
  footer{color:var(--soft);font-size:11px;text-align:center;padding:18px}
  footer .build{margin-top:6px;font-family:var(--mono);font-size:10px;opacity:.8}

  /* Item 295. A confirming action beside a way out, wherever it appears:
     the confirm pop-up, the report pop-up, the edit page, the four cleanup
     confirmations and the Import Builder's own confirm. Andy, with a
     screenshot of a Mac dialog: *"On my Mac save is always on the right ...
     I think we should stick to this format"*.

     One rule rather than the five it replaces, because a value repeated in
     five places drifts, which is how the Toolbox came to do it three ways
     at once. The way out goes first in the markup and so appears on the
     left; the thing that acts is last, at the right, where the eye
     finishes. It wraps rather than reversing on a narrow screen: putting
     the primary back on top there would mean the tab order and the drawn
     order disagreeing, which is worse than a second line.

     It is a modifier, not a replacement: `.modal-actions` keeps its stacked
     full-width shape for the dialogs that offer two real alternatives
     rather than an action and a way out (the Import Builder's "check images
     first?" and its profile save), where two even columns would squeeze
     labels that are whole sentences. **The Import Builder's frame cannot
     read this file**, so `import_builder/toolbox.css` carries a second copy
     and `test_actions295.py` holds the two together.

     BASE_CSS's `.modal-actions button{width:100%}` is a class AND an
     element, so `.actionrow > *` loses to it on specificity and every
     button wraps onto a line of its own - which draws exactly like the
     column it was meant to replace. Naming the elements matches that
     specificity, and APP_CSS being emitted later settles the tie. */
  .actionrow{display:flex;flex-direction:row;justify-content:flex-end;
    align-items:center;gap:10px;flex-wrap:wrap;margin-top:18px;
    padding-top:16px;border-top:1px solid var(--line)}
  .actionrow > button,.actionrow > a,.actionrow > label{width:auto;flex:none}

  /* Item 150 - a bigger tick with a big hit area. The label is the whole
     left strip of the row, full height, so a click anywhere down the left
     edge ticks rather than folding the row open. The first attempt used
     negative margins and covered only the title line (36px of a 59px row),
     which left the second line's edge as a miss. A ticked row is outlined,
     so a miss is visible before approving rather than after. */
  .cleanrow .pickhit{position:absolute;left:0;top:0;bottom:0;width:38px;
    display:flex;justify-content:center;align-items:center;
    cursor:pointer;border-radius:8px 0 0 8px}
  /* Centred while collapsed (Andy: it sat too near the top). Once Why is
     open the row grows, so the box goes back to where it sat when collapsed
     rather than floating halfway down the evidence. 20px is (59 - 18) / 2,
     the collapsed height of a one-line title. */
  .cleanrow:has(> details.mini[open]) .pickhit{align-items:flex-start;
    padding-top:20px}
  .cleanrow.tickable{padding-left:44px}
  .cleanrow .pickhit:hover{background:var(--accent-soft)}
  .cleanrow .pickhit input.pick{width:18px;height:18px;margin:0;
    cursor:pointer;accent-color:var(--accent)}
  .cleanrow:has(input.pick:checked){border-color:var(--accent);
    box-shadow:inset 3px 0 0 var(--accent)}
  /* Item 213 - the parts of a seller, as a band of the chrome rather than
     furniture on the page (Andy's design, 14 September).
     
     Item 212 put these on the page as pills, above the cleanup switcher's
     big boxed tabs - two navigations of the same kind at different sizes,
     and the bigger one moved you the shorter distance. In the chrome they
     are a different surface instead of a louder version of the same one, so
     nothing below them has to compete.
     
     The active part is cut out of the band in the page's own colour and
     joins the content under it, which is the whole trick: it says "this
     band and that page are one thing" without a word. */
  /* Item 290 (report #39). Every part is the same tab shape: clear of the
     top of the band by 4px, rounded at the top corners, with a little air
     at each side. Only the colour tells them apart, so a hover draws the
     tab the active part draws rather than a full-height block, and no two
     of them ever touch. Andy: *"when you hover over a none active tab it
     should look like a tab not full height"*, and *"add a slight margin to
     the side of tabs so they never hit each other or the borders"*.

     The dividing lines went with it. They were the only thing separating
     one part from the next, and a line touching a rounded tab is exactly
     what the second of those asks is about; the space does the separating
     now. */
  .partsbar{background:var(--brand-3);display:flex;align-items:stretch;
    gap:6px;padding:0 26px;flex-wrap:wrap}
  /* Item 309 (Andy): *"make the lighter tab colour that occurs on hover show
     before you hover so you can see the tab shapes before going over them.
     Then on hover make them even lighter"*. Item 290 gave every part the
     same tab SHAPE, but an inactive one was the band's own colour until you
     found it with the cursor, so the shapes were invisible at rest and the
     band read as one bar with a cream tab stuck on it. Now the shape is
     always drawn and hovering lightens it further, so the tabs read as tabs
     and the hover still answers. */
  .partsbar .part{display:flex;flex-direction:column;gap:2px;justify-content:center;
    padding:9px 18px 10px;color:rgba(244,241,234,.86);text-decoration:none;
    font-size:13.5px;font-weight:600;line-height:1.25;
    margin-top:4px;border-radius:6px 6px 0 0;
    background:rgba(255,255,255,.08)}
  .partsbar .part:hover{background:rgba(255,255,255,.18);color:#fff}
  .partsbar .part .ps{font-size:11.5px;font-weight:400;
    color:rgba(244,241,234,.72);text-transform:none}
  /* The strip's colour exactly, no line of its own along the bottom: the
     tab and the strip under it have to read as one shape, and a border or a
     shadow across the band is the seam that gives it away. */
  .partsbar .part.on{background:var(--strip);color:var(--ink)}
  .partsbar .part.on:hover{background:var(--strip);color:var(--ink)}
  /* The strip itself: full width, the seller's own line inside it, lined up
     with the page column below. */
  .sellerstrip{background:var(--strip)}
  /* One height for every part's strip (item 290). Andy, answering the
     report's own question about what the Settings bar should carry: *"also
     make sure the height of this bar stays the same across all tabs"*. It
     did not: 38px of chips on the Price Checker, 42 once a badge was beside
     them, 49 on Catalogue cleanup, where the strip holds buttons. The band
     is chrome, and chrome that changes height as you move between parts
     makes the page jump under the cursor. 49px is the tallest thing that
     legitimately goes in it, a row of buttons, so that is the height. */
  .sellerstrip .stripin{max-width:1600px;margin:0 auto;padding:9px 22px;
    display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-height:49px}
  .sellerstrip .filebar{margin:0}
  .partsbar .part.on .ps{color:var(--muted)}
  /* Not a part: the way out, and the seller's runs. Pushed to the right,
     and never styled as a tab - they do not join the page below. */
  .partsbar .partaway{margin-left:auto;display:flex;align-items:center;gap:16px;
    padding:0 2px 0 18px}
  .partsbar .partaway a{color:rgba(244,241,234,.88);text-decoration:none;
    font-size:12.5px;white-space:nowrap}
  .partsbar .partaway a:hover{color:#fff;text-decoration:underline}
  @media (max-width:760px){
    .partsbar{padding:0 12px}
    .partsbar .part{padding:8px 12px}
    .partsbar .partaway{margin-left:0;padding:8px 12px}
  }

  /* Items 155-157 - the Catalogue cleanup screen switcher. */
  .screens{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0 10px}
  .screens a{flex:1 1 200px;display:flex;justify-content:space-between;
    align-items:center;gap:10px;padding:10px 14px;border:1px solid var(--line);
    border-radius:8px;background:#fff;color:var(--ink);text-decoration:none;
    font-weight:600;font-size:14px}
  .screens a:hover{border-color:var(--accent-line);background:var(--accent-soft)}
  .screens a.on{border-color:var(--accent);box-shadow:inset 0 -3px 0 var(--accent)}
  .screens .n{font-weight:500;font-size:12.5px;color:var(--muted);white-space:nowrap}
  .screens .n.work{color:var(--warn,#9a5b00)}
  .screens .n.ok{color:var(--accent)}
  table.offlist td{vertical-align:middle}
  table.offlist td:first-child,table.offlist td:last-child{white-space:nowrap}
  /* Item 147 - "Compare all 3 in the portal" on each cleanup row. */
  .cleanrow .portalcmp{margin-left:10px;font-size:12px;white-space:nowrap;
    color:var(--accent);text-decoration:underline;text-underline-offset:2px}
  .cleanrow .portalcmp:hover{color:var(--brand)}
  /* Item 150, follow-up. The Compare link and the Ad ID links inside Why sit
     on a row that folds when you miss them. A few pixels of padding widens
     the target, and the matching negative margin gives it back, so nothing
     on the line moves. Kept small on purpose: the Ad lines are close
     together, and a bigger box would overlap its neighbours. */
  .cleanrow .cleanact a, .cleanrow details.mini a{display:inline-block;
    padding:3px 5px;margin:-3px -5px}
  .cleanrow .cleanact a.portalcmp{margin-left:5px}
  /* Item 144 - the "Updated ..." line under each sellers-list button. */
  .stamped{display:flex;flex-direction:column;align-items:center;gap:3px}
  .stamped .stamp{font-size:11.5px;line-height:1.2;white-space:nowrap}
  /* Item 65. BASE_CSS caps the page column at 1180px - inherited from the
     Import Builder theme this file's base is extracted from, never a
     decision made for this app, and never revisited until Andy asked why
     a wide monitor was not being used.

     Measured before changing it (Playwright at 2560px, real-length seller
     names and product titles): at 1180px the sellers list table renders
     1136px wide, so nothing was being cut off - content was being
     *truncated early* by .sellerlink and td.notes below, both of which
     cap independently of the page width. Raising this alone would only
     have widened the padding, which is why all three move together.

     1600px rather than no cap at all: at full stretch on a 2560px monitor
     a card's intro paragraph runs as one ~2,500px line, and the sellers
     list's numbers sit far enough from the seller names to make scanning
     a single row harder. 1600 fits a 40-character seller name whole and a
     full product title plus its note on one line, which was the whole
     ask. Nothing changes below 1600px. Andy chose this from side-by-side
     screenshots of all three options.

     Overrides BASE_CSS by sitting later in the file at equal specificity -
     do not hand-edit the BASE_CSS rule itself, extract_theme.py will
     silently put it back. */
  .wrap { max-width: 1600px; }

  /* A small (i) button that pops the shared info modal (see INFO_MODAL_HTML
     above) - sized to sit inline right after a label.fld's uppercase text
     without dominating it, not to match that text's own scale. */
  .infoicon{display:inline-flex;align-items:center;justify-content:center;
    width:15px;height:15px;border-radius:50%;border:1px solid var(--line);
    background:var(--panel);color:var(--muted);font:italic 600 10px Georgia,serif;
    line-height:1;cursor:pointer;padding:0;margin-left:5px;vertical-align:middle}
  .infoicon:hover{color:var(--ink);border-color:var(--soft);background:var(--paper)}
  /* The base <details> border/padding box (see BASE_CSS) suits a standalone
     section - "Choose from N variants", the technical log - but reads as a
     button when it wraps one small toggle sitting inline inside a table
     cell, like the Linked tab's per-row "Details". This strips the box back
     to plain text for just that case, leaving every other <details> alone. */
  details.mini { border: none; padding: 0; margin-bottom: 0; display: inline-block; }

  /* The app's identity (logo, name, version) used to be folded into
     whatever title a page passed in, which put a version badge next to a
     seller's own name on the seller detail page. Now it is its own fixed
     block stacked under the logo, left of the page's own heading - see
     page() in this file. */
  header.top .brand{display:flex;flex-direction:column;align-items:center;gap:4px}
  header.top .brand .appname{font-family:var(--mono);font-size:10.5px;
    color:#a9c6b8;letter-spacing:.05em;text-transform:uppercase;
    display:flex;align-items:center;justify-content:center}

  /* The Import Builder only ever styles links inside .flagrow and footer, so
     every other link fell back to browser purple. */
  a { color: var(--accent, #1f6f4f); text-decoration: none; }
  a:hover { text-decoration: underline; text-underline-offset: 2px; }
  a:visited { color: var(--accent, #1f6f4f); }
  table a { text-decoration: underline; text-underline-offset: 2px; }
  /* .rowbadge (BASE_CSS) is a pill, not a plain link - the table-wide
     underline above would cut straight through it. Used for the sellers
     list's "Amazon" flag on rows with a confirmed seller ID. */
  table a.rowbadge, table a.rowbadge:hover { text-decoration: none; }
  table a.badge, table a.badge:hover { text-decoration: none; }

  /* Site-wide nav in the header's top-right corner. One link today
     ("All sellers"), but a <nav> holding real <a> pills rather than a
     single hardcoded link so more can be added later without redoing
     this. */
  header.top .topnav { display: flex; gap: 8px; }
  header.top .navlink {
    font-family: var(--mono); font-size: 11px; color: #fff;
    background: rgba(255,255,255,.16); border: 1px solid rgba(255,255,255,.25);
    padding: 4px 12px; border-radius: 20px; letter-spacing: .03em;
    text-decoration: none;
  }
  header.top .navlink:hover { background: rgba(255,255,255,.28); text-decoration: none; }
  header.top .navlink:visited { color: #fff; }
  header.top .topnav { display: flex; gap: 8px; align-items: center; }

  header.top button.navlink {
    cursor: pointer; -webkit-appearance: none; appearance: none;
    font: inherit; letter-spacing: inherit;
  }

  /* There is no .badge in the Import Builder - it uses .pill. Verdicts want
     a solid colour rather than a tint, so this is a filled sibling of .pill
     with the same shape. */
  .badge {
    display: inline-block;
    padding: 3px 10px;
    border-radius: 20px;
    color: #fff;
    /* A literal fallback on every one of these. An undefined custom property
       makes the declaration invalid, which left white text on no background
       at all - and the failure is silent, so it only shows up when someone
       looks at the screen. */
    background: #7f8c8d;
    font-family: var(--mono, monospace);
    font-size: 10.5px;
    letter-spacing: .03em;
    white-space: nowrap;
    font-weight: 500;
  }
  /* .badge is a <span> everywhere except the sellers-list undercut flag
     (item 13), which is a link so it can jump straight to that seller's
     filtered Results tab. A clicked one then matches the global
     `a:visited{color:var(--accent)}` rule (0,1,1 specificity) which beats
     plain `.badge{color:#fff}` (0,1,0) - dark green text on a dark red
     pill, unreadable. `.badge:visited` (0,2,0) outranks it. */
  .badge:visited { color: #fff; }
  /* The Import Builder's palette is --stop / --warn / --ok / --soft. It has
     no --undercutting, --matched or --neutral: those belong to
     html_report.py, and using them here is what made the badge disappear. */
  /* Item 73: how long a seller has been quiet, in three bands Andy chose -
     30 days light amber, 90 orange, 180+ red. Outlined rather than filled,
     like .badge.not_selling above, so a column of these reads as annotation
     beside the Orders count rather than competing with the filled
     Competitiveness verdicts a few columns along.

     Literal fallbacks on every colour, for the reason spelled out at the
     top of .badge: an undefined custom property invalidates the whole
     declaration, silently. --stop and --warn exist in BASE_CSS; there is no
     token for the lightest band, so it carries its own value. */
  .badge.quiet {
    background: transparent;
    border: 1px solid currentColor;
    font-weight: 500;
  }
  .badge.quiet.q30  { color: #c98a3c; }
  .badge.quiet.q90  { color: var(--warn, #9a5b00); }
  .badge.quiet.q180 { color: var(--stop, #b50000); }
  /* A seller we have switched off ourselves keeps the number and loses the
     colour - being quiet is the expected consequence there, not a finding,
     and the loudest thing on the row should never be the one that needs no
     attention. */
  .badge.quiet.qoff { color: var(--soft, #a2a294); }
  .badge.undercutting { background: var(--stop, #b50000); }
  .badge.matched      { background: var(--warn, #9a5b00); }
  .badge.competitive  { background: var(--ok, #2f6b4f); }
  .badge.unknown      { background: var(--soft, #a2a294); }
  .badge.running      { background: var(--accent, #1f6f4f); }
  .badge.done         { background: var(--ok, #2f6b4f); }
  .badge.failed       { background: var(--stop, #b50000); }
  /* Not a verdict - a flag saying our side of the comparison is not live.
     Outlined rather than filled so it reads as an aside next to a product
     name, not as a second verdict competing with the one in the column. */
  .badge.not_selling {
    background: transparent;
    color: var(--warn, #9a5b00);
    border: 1px solid var(--warn, #9a5b00);
    font-weight: 500;
  }
  /* Item 290: how old this seller's catalogue read is, in the cream strip
     under the parts band, where the Settings part now carries it. The same
     outlined pill as .badge.quiet above and for the same reason - it is an
     annotation about the age of something, not a verdict - and it sits
     beside .badge.not_selling, which is the other thing that bar can hold,
     so they have to be one shape. The steps are the two the age already has
     everywhere else: STALE_HOURS amber, GUESSWORK_HOURS red. The green
     header's own chip cannot be reused; its colours are for a dark band. */
  .badge.age {
    background: transparent;
    color: var(--muted, #6c7169);
    border: 1px solid var(--muted, #6c7169);
    font-weight: 500;
  }
  .badge.age.warm { color: var(--warn, #9a5b00); border-color: var(--warn, #9a5b00); }
  .badge.age.cold { color: var(--stop, #b50000); border-color: var(--stop, #b50000); }

  /* Traffic-light columns on the sellers list (item 16) - a plain
     coloured circle rather than an SVG/font glyph, consistent with the
     rest of the app's habit of using real CSS/markup over icon assets
     where a shape this simple doesn't need one. Same --stop/--warn/--ok
     palette as .badge.undercutting/matched/competitive, so a count here
     and its equivalent verdict badge on the Results tab always agree. */
  .lightdot {
    display: inline-block;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    vertical-align: middle;
  }
  /* Long seller names used to be free to run as wide as they liked - fine
     with one flag at the end of the row, less fine now three more sortable
     columns need the room (item 16). Truncates the name only, not the
     Amazon flag that follows it; the full name is still the page's own h1
     one click away, and sits in the title attribute for a hover. */
  .sellerlink {
    display: inline-block;
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
  }
  /* Every table in the app already wraps in .table-wrap; it just never had
     anything to do before now. A row wide enough to overflow on a small
     window scrolls inside its own box instead of the page. */
  .table-wrap { overflow-x: auto; }

  /* Run progress. A script that emits PROGRESS lines gets a live bar on its
     run page: one animated fill, one label row with the caption on the left
     and the percentage on the right, refreshed by the page's own 3-second
     reload rather than anything client-side. */
  .progresswrap { margin: 0 0 16px; }
  .progresswrap.failed .progressfill { background: var(--stop, #b50000); }
  .progresswrap.failed .progresslabel { color: var(--stop, #b50000); }
  .progressbar {
    height: 10px;
    border-radius: 999px;
    background: var(--panel, #f4f2ec);
    border: 1px solid var(--line, #ddd8c8);
    overflow: hidden;
  }
  .progressfill {
    height: 100%;
    background: var(--accent, #1f6f4f);
    border-radius: 999px;
    transition: width 0.6s ease;
  }
  .progresslabel {
    display: flex;
    justify-content: space-between;
    font-family: var(--mono);
    font-size: 11.5px;
    color: var(--muted);
    margin-top: 6px;
  }

  /* Tabs on a page with several large tables - the seller page's answer to
     "what if Linked has 12,000 rows". Plain links with a query string, no
     JS, so the active tab survives a reload and is bookmarkable/shareable.
     A count badge reuses .pill.role, already used elsewhere for "N to
     review" style callouts, so a tab with something waiting looks the same
     as it would anywhere else in the app. */
  .tabbar {
    display: flex;
    gap: 4px;
    border-bottom: 1px solid var(--line, #ddd8c8);
    margin: 4px 0 18px;
  }
  .tabbar .tab {
    padding: 8px 14px;
    font-size: 13px;
    color: var(--muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
  }
  .tabbar .tab:hover { color: var(--ink); }
  .tabbar .tab.active {
    color: var(--ink);
    border-bottom-color: var(--accent, #1f6f4f);
    font-weight: 600;
  }
  .tabbar .tab .pill { margin-left: 6px; }

  /* Simple prev/next paging for a table that could run long. */
  .pager {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 10px;
  }
  .pager a { font-size: 12.5px; }

  /* Right-aligned figures in tables read better tabular. */
  td.num, th.num { font-variant-numeric: tabular-nums; }
  /* Competitiveness and its three dots are one heading, not two rows: no line
     between them, and the dots tucked up under the word (Andy, 11 September). */
  th.grouphead{border-bottom:none;padding-bottom:1px}
  th.lighthead{padding-top:0;padding-bottom:5px}

  /* A row whose meaning is "look at this" deserves to be findable - but
     the red verdict badge already says so on its own, so this only needs
     to help the eye scan a long list, not shout. A very light neutral
     tint does that; the old pink (--stop-soft) fought with the badge and
     read as an error rather than a nudge to look closer. */
  tr.attention td { background: var(--panel, #fcf8f3); }

  /* One filter bar rather than a row of loose controls. Everything sits on a
     single baseline, the search takes the space it deserves, and the gap
     property does the spacing so no element carries its own margins. */
  .filterbar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    background: var(--paper, #fff);
    border: 1px solid var(--line, #e7dcc4);
    border-radius: 12px;
    padding: 12px 14px;
    margin: 0 0 16px;
  }
  .filterbar .searchwrap { position: relative; flex: 1 1 280px; min-width: 240px; }
  .filterbar .searchwrap svg {
    position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
    width: 15px; height: 15px; color: var(--soft, #a2a294); pointer-events: none;
  }
  .filterbar input[type=search] {
    width: 100%;
    padding: 10px 16px 10px 38px;
    border: 1px solid var(--line, #e7dcc4);
    border-radius: 999px;
    background: #fff;
    font-family: var(--sans, system-ui);
    font-size: 13.5px;
    -webkit-appearance: none;
  }
  .filterbar input[type=search]:focus {
    outline: 2px solid var(--accent-line, #a8d3c1);
    outline-offset: -1px;
    border-color: var(--accent-line, #a8d3c1);
  }
  .filterbar select {
    width: auto;
    padding: 9px 12px;
    border-radius: 999px;
    border: 1px solid var(--line, #e7dcc4);
    background: #fff;
    font-size: 13px;
  }
  .filterbar button { border-radius: 999px; padding: 9px 18px; }
  .filterbar .count { margin-left: auto; white-space: nowrap; }

  /* Checkboxes as toggle chips: same pill language as everything else, and a
     far bigger hit area than a 13px box. */
  .toggle {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 8px 14px;
    border: 1px solid var(--line, #e7dcc4);
    border-radius: 999px;
    background: #fff;
    font-size: 12.5px;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
  }
  .toggle:hover { border-color: var(--accent-line, #a8d3c1); }
  .toggle input { margin: 0; }
  .toggle.on {
    background: var(--accent-soft, #e4f0ea);
    border-color: var(--accent, #1f6f4f);
    color: var(--accent, #1f6f4f);
    font-weight: 500;
  }

  /* summary.toggle (item 26's disclosure, renamed "Filter" in item 30)
     reuses the same pill as the checkbox toggles above, so it doesn't read
     as a different kind of control - just strip the browser's own
     disclosure marker, which .toggle knows nothing about. */
  summary.toggle { list-style: none; }
  summary.toggle::-webkit-details-marker { display: none; }

  /* The summary used to be a single static "More filters (down arrow)"
     label that never changed once opened - Andy pointed out the arrow
     kept pointing down after expanding. Two spans, toggled purely by the
     <details> element's own [open] attribute (no JS needed), swap the
     label and the arrow direction together. */
  summary.toggle .when-open { display: none; }
  details[open] > summary.toggle .when-open { display: inline; }
  details[open] > summary.toggle .when-closed { display: none; }

  /* "Filter" (item 26, renamed and expanded in item 30) - originally a
     home for filters used less than "every visit"; now holds every filter
     except Search, since Andy wants room to keep adding more over time
     without the main bar growing forever. Plain <details>, styled to sit
     in the filterbar rather than as its own boxed section: no border/
     padding of its own (the base details{} box in BASE_CSS is switched
     off here), and forced to the full row width only once opened, so its
     panel gets its own line rather than squeezing between whatever else
     is on the row. */
  /* Toolbox: a drop-down panel under the Filter button (Andy: "a header
     when its open on the left 'Filter results' with the close filter
     button ... should be on a drop down menu really like we did on the
     other screens"), opening over the table instead of pushing it down. */
  .filterbar details.advfilters { border: none; padding: 0; margin: 0; position: relative; }
  /* Positioned by FILTER_PANEL_JS so it always fits the window: under the
     button where there is room, slid along to stay on screen where there
     is not (Andy: "goes off the left of my browser window ... even though
     there is plenty of room"). right:0 is only the no-script fallback.
     Fades and settles in (Andy: "fade in quickly rather than just appear"),
     over a faint, soft shadow and a lightly frosted background. */
  .filterbar details.advfilters .advfilters-panel {
    position: absolute; z-index: 60; top: calc(100% + 8px); right: 0;
    width: min(720px, calc(100vw - 32px));
  }
  .afhead { display: flex; align-items: center; justify-content: space-between;
    padding: 12px 14px 10px 18px; border-bottom: 1px solid var(--line, #e7dcc4); }
  .afhead h3 { margin: 0; font-size: 14px; font-weight: 600; }
  .filterbar .afclose { width: 32px; height: 32px; padding: 0; flex: 0 0 auto; display: inline-flex; align-items: center;
    justify-content: center; border-radius: 8px; border: 1px solid var(--line, #e7dcc4);
    background: #fff; color: var(--ink-2, #33403a); cursor: pointer; }
  .filterbar .afclose:hover { border-color: var(--accent, #1f6f4f); color: var(--accent, #1f6f4f); }
  .afbody { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 28px; padding: 14px 18px 16px;
    max-height: min(70vh, 560px); overflow: auto; }
  .afbody section.wide { grid-column: 1 / -1; }
  .afcol { display: flex; flex-direction: column; gap: 16px; }
  .afbody h4 { margin: 0 0 8px; font-family: var(--mono); font-size: 10.5px; font-weight: 600;
    letter-spacing: .06em; text-transform: uppercase; color: var(--muted, #6c7169); }
  .afchecks { display: flex; flex-direction: column; gap: 7px; }
  .afcheck { display: flex; align-items: center; gap: 9px; font-size: 13px; cursor: pointer; }
  .afcheck input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent, #1f6f4f); }
  .afsels { display: flex; flex-direction: column; gap: 8px; }
  .afsel { display: grid; grid-template-columns: 60px 1fr; align-items: center; gap: 8px; font-size: 13px; }
  .afsel select { width: 100%; }
  .afranges { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 28px; }
  .afrange { display: grid; grid-template-columns: 1fr 64px auto 64px; align-items: center;
    gap: 6px; font-size: 13px; }
  .afrange input[type=number] { width: 64px; padding: 5px 6px; text-align: center; -moz-appearance: textfield; }
  .afrange input[type=number]::-webkit-outer-spin-button,
  .afrange input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
  .affoot { display: flex; justify-content: space-between; align-items: center; gap: 12px;
    padding: 10px 18px; border-top: 1px solid var(--line, #e7dcc4); font-size: 12.5px;
    color: var(--muted, #6c7169); }
  @media (max-width: 600px) {
    /* On a phone there is no room to hang a panel from the button: it
       takes the screen instead. */
    .filterbar details.advfilters .advfilters-panel {
      position: fixed; left: 12px; right: 12px; top: 12px; bottom: 12px; width: auto;
      display: flex; flex-direction: column; }
    .afbody { flex: 1 1 auto; max-height: none; }
    .afbody, .afranges { grid-template-columns: 1fr; }
  }

  /* Number-range filters (item 30) - "Field greater/less than X" as a
     pair of plain number boxes in one pill. The global
     select,input[type=number] rule sets width:100%, right for a field
     alone on its own row but not for two of them sharing a pill; scope
     these back down to a size that fits a few digits comfortably. */
  .numfilter {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border: 1px solid var(--line, #e7dcc4);
    border-radius: 999px;
    background: #fff;
    font-size: 12.5px;
    white-space: nowrap;
  }
  .numfilter input[type=number] {
    width: 46px;
    padding: 5px 4px;
    text-align: center;
    /* The browser's built-in up/down spinner otherwise eats enough width
       to clip the "max" placeholder to "ma" - these are plain typed
       numbers, not steppers, so the spinner just costs space for nothing. */
    -moz-appearance: textfield;
  }
  .numfilter input[type=number]::-webkit-outer-spin-button,
  .numfilter input[type=number]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }

  /* Always-visible readout of the current filter state (item 30), so it
     stays legible even with the Filter panel collapsed back up. Its own
     line in the filterbar's flex layout - same flex-basis:100% treatment
     the panel itself gets once open. */
  .filtersummary {
    flex-basis: 100%;
    font-size: 12px;
    color: var(--soft, #8b8b7a);
  }

  /* Plain text link rather than a pill or button - resetting isn't a
     filter choice itself, so it shouldn't look like one of the toggles
     next to it. Only rendered once a filter is actually active. */
  .resetlink {
    font-size: 12.5px;
    color: var(--soft, #8b8b7a);
    text-decoration: underline;
    white-space: nowrap;
  }
  .resetlink:hover { color: var(--accent, #1f6f4f); }

  /* Notes wrap rather than being cut. They explain why a verdict might be
     wrong, and half a warning reads as a whole one. */
  td.notes {
    white-space: normal;
    max-width: 380px;
    line-height: 1.45;
  }

  /* The run-status modal (see RUN_MODAL_SCRIPT in app.py). Shown/hidden by
     toggling the [hidden] attribute from JS - the browser's own default
     [hidden]{display:none} does the hiding, so this only has to style what
     it looks like once visible. */
  .runmodal { position: fixed; inset: 0; z-index: 95; }
  .runmodal-backdrop { position: absolute; inset: 0; background: rgba(20,26,22,.55); }
  .runmodal-panel {
    position: relative; max-width: 560px; width: 92vw; margin: 12vh auto;
    background: #fff; border-radius: 12px; padding: 24px;
    box-shadow: 0 10px 40px rgba(0,0,0,.25);
  }
  .runmodal-head { font-weight: 600; font-size: 14.5px; margin-bottom: 14px; }
  .runmodal-actions { margin-top: 16px; display: flex; gap: 10px; align-items: center; }
  .runspinner {
    width: 26px; height: 26px; border: 3px solid var(--line);
    border-top-color: var(--accent); border-radius: 50%;
    animation: runspin .8s linear infinite; display: inline-block;
    flex-shrink: 0;
  }
  /* Sized to sit on one text line inside a note box, rather than the large
     standalone circle used for the modal's initial "Starting..." state. */
  .runspinner.sm { width: 14px; height: 14px; border-width: 2px; }
  @keyframes runspin { to { transform: rotate(360deg); } }

  /* Item 65, the half that actually widens content rather than the page.
     .sellerlink (260px, item 16) and td.notes (380px) cap independently of
     the page width, so raising .wrap alone would only have added padding.

     Gated on the viewport rather than applied outright: at 1180px the
     sellers list table fits at exactly 1136px, and raising these caps
     there pushed it into horizontal scrolling - trading early truncation
     for something worse. Measured, not assumed. 1600px is the width the
     page now stops growing at, so above it the extra 420px is guaranteed
     and 340px of it is spent here.

     420px fits every seller name in the real database whole; 560px fits
     the clippable-voucher note (the longest this app writes, and the one
     that most needs reading whole) on one line rather than three. The
     title attribute on a truncated name stays either way. */
  @media (min-width: 1600px) {
    .sellerlink { max-width: 420px; }
    td.notes { max-width: 560px; }
  }

  /* ------------------------------------------------------------------
     Items 91-110, moved here on 9 September. Every one was written into
     BASE_CSS by mistake, and BASE_CSS is regenerated by extract_theme.py:
     the next extract would have taken the lot without a word. APP_CSS is
     emitted after BASE_CSS, so a same-specificity override here still wins
     on source order - which several of these rules depend on.
     ------------------------------------------------------------------ */
  /* A stat label long enough to be a phrase wraps instead of widening the
     row: pushing the figures beside it out of view to keep one label on a
     single line is the wrong trade (Andy, 13 September - the sellers list's
     six figures). A seller page stat has more room than the list's. */
  .stat .s{max-width:150px}
  .statwide .s{max-width:230px}
  /* The page furniture reads as a band attached to the green header rather
     than as the first line of the content. Andy: "make the top line look
     more like its part of the green bar or attached to it... this will then
     make that look like a sub title almost to the page with the main summary
     info for this section and the settings that update everything."
     Negative side margins take it out to the wrap's padding edge and a
     negative top margin closes the gap under the header, so the band is the
     full width of the column and touches the nav. Scoped to a filebar that
     really is the first thing in the wrap - anywhere else it stays an
  /* Done, meaning the catalogue was read back and matches. The one state on
     a cleanup row that reports fact rather than intent, so it gets the
     palette's --ok rather than another tinted grey. */
  .pill.ok{background:var(--ok-soft,#e6f0ea);color:var(--ok,#2f6b4f);
    border:1px solid var(--ok,#2f6b4f)}
  /* A confirm dialog's optional third way out, kept visually below the two
     real answers so it never competes with them. */
  .modal-extra{margin-top:12px;padding-top:11px;border-top:1px solid var(--line);
    display:flex;align-items:center;gap:9px;flex-wrap:wrap}
  .modal-extra .muted{font-size:11.5px;line-height:1.5;flex:1;min-width:150px}
  /* display:flex above beat the browser's own [hidden]{display:none}, so a
     confirmation with no extra action showed an empty button under a rule. */
  .modal-extra[hidden]{display:none}
  /* Item 206 - several alternatives, each its own row with its own reason.
     The separator moves to the container so two rows are not divided from
     each other, only from the decision above them. */
  .modal-extras{margin-top:12px;padding-top:11px;border-top:1px solid var(--line);
    display:flex;flex-direction:column;gap:10px}
  .modal-extras[hidden]{display:none}
  .modal-extras .modal-extra{margin-top:0;padding-top:0;border-top:none}
  /* The finished-work tabs, pushed right and lighter: still tabs, but plainly
     not where the work is. */
  .tabbar .tabgap{flex:1}
  .tabbar .tab.past{color:var(--soft,#8a9490)}
  .tabbar .tab.past .pill.role{background:var(--panel);color:var(--muted)}
  .tabbar .tab.past.active{color:var(--ink)}
  /* A filebar chip that is stating a problem rather than a fact - the age of
     the stock behind the queue, once it is old enough to matter. Same shape
     as its neighbours so the bar still reads as one row. */
  .filebar .fname.warnchip{background:var(--warn-soft,#f8ecd6);
    color:var(--warn,#9a5b00);border:1px solid var(--warn,#9a5b00)}
  /* The stock age, as a caption under the button that refreshes it rather
     than a third chip beside two advert counts. The form goes column so the
     note sits under the button; the negative margin keeps the filebar's own
     line height from growing by the height of the caption. */
  /* The catalogue's age sits in the green header beside the page title,
     shaped like the version badge next to the logo - Andy: "could be in a
     rounded box similar to the version number which gets bolder or colour
     changes as it gets older". It belongs there rather than in the band
     because it is a fact about this whole section, not about one control,
     and putting it there let the band go back to one line. */
  header.top h1 .agebadge{font-family:var(--mono);font-size:10.5px;
    font-weight:400;color:#cfe3d8;background:rgba(255,255,255,.12);
    border:1px solid rgba(255,255,255,.2);padding:2px 9px;border-radius:20px;
    letter-spacing:.04em;vertical-align:middle;margin-left:10px;
    white-space:nowrap}
  /* Three steps rather than two, because "old" is not one thing: a day is
     worth noticing and a week is worth stopping for. */
  header.top h1 .agebadge.warm{color:#ffe3b0;background:rgba(255,196,92,.18);
    border-color:rgba(255,196,92,.45)}
  header.top h1 .agebadge.cold{color:#fff;background:rgba(214,90,74,.42);
    border-color:rgba(255,160,148,.65);font-weight:600}
  /* Every way back carries the same arrow, so a link that returns you
     somewhere is recognisable before it is read. One rule rather than the
     character typed into eight strings - it cannot then drift, and a ninth
     back link only has to remember the class. */
  a.back::before{content:"←";margin-right:7px;display:inline-block;
    font-size:15px;line-height:1;vertical-align:-1px}
  .cleanrow.done{background:#fbfdfb;border-color:#dfe9e3}
  /* The Why toggle sits on the row's own top line rather than a line of its
     own. Placed after the base .cleanrow rules deliberately: same
     specificity, so source order decides, and the first attempt at this lost
     silently and left the padding untouched.

     `display:block` on the collapsed <details> matters as much as the
     position: an empty inline-block still sits on a text baseline and makes
     a 16px line box out of nothing. */
  .cleanrow{position:relative;padding:7px 12px}
  .cleanrow > details.mini{display:block;margin:0}
  /* The page furniture reads as a band attached to the green header rather
     than as the first line of the content. Andy: "make the top line look
     more like its part of the green bar or attached to it... this will then
     make that look like a sub title almost to the page with the main summary
     info for this section and the settings that update everything."

     Negative side margins take it out to the wrap's padding edge and a
     negative top margin closes the gap under the header. Scoped to a filebar
     that really is the first thing in the wrap - anywhere else the negative
     top margin would drag it over whatever sits above it.

     The bottom padding is deliberately deeper than the top: the Update
     catalogue button carries a caption under it, and the band has to contain
     that rather than let it hang below the border. */
  .wrap > .filebar:first-child{
    margin:-22px -22px 8px;padding:8px 22px;
    background:#e3dbc7;border-bottom:1px solid #d3c9b0}
  /* Closer to the band above, now that the band is the thing it belongs
     under rather than a line of text it has to clear. */
  .wrap > .filebar:first-child + .tabbar{margin-top:0}
  /* Item 286. The four button styles on <a>, <button> and <label> alike.

     BASE_CSS gives the button box to `button` and to `a.ghost`, and to
     nothing else. So `.accent` reached only a <button>, and no style at all
     reached a <label>. Three things came of that:

     - The sign-in page's "Sign in with Microsoft", Catalogue cleanup's
       "Download the sheet" and Needs attention's "Open the review queue"
       are all <a class="accent ...">, and all three rendered as plain green
       text where a button belongs.
     - app.py hand-styled one of them with padding and a radius because the
       class alone did nothing. It still did not look like a button: the
       inline style set no background.
     - Item 285's <label class="button accent"> rendered as plain text, and
       CLAUDE.md had been naming a `.button` class the CSS never had.

     So `.button` is a real class now, for the two elements that cannot be a
     <button>, and `.accent` and `.ghost` raise the box on their own the way
     `a.ghost` always has. Nothing here changes a <button>.

     These rules are in APP_CSS rather than beside the ones they extend:
     extract_theme.py regenerates BASE_CSS from the Import Builder and would
     take them out without a word. APP_CSS is emitted straight after it, so
     an equal-specificity rule here still wins.

     The box below is BASE_CSS's `button,a.ghost` rule copied declaration for
     declaration, which is a value in two places. test_buttons286.py asserts
     the two agree, so a re-extract that changes the button box fails the
     suite rather than leaving half the Toolbox behind. */
  /* Item 288. A fourth level for note(): "this is refused", as against
     warn's "this needs care". The colours are the ones --stop/--stop-soft
     already carry, and .flagrow.stop already uses them to mean this; BASE_CSS
     defines .note.info/.warn/.ok and stops there. */
  .note.stop{background:var(--stop-soft);color:var(--stop)}
  a.button,label.button,a.accent,label.accent,label.ghost{font-family:var(--sans);font-size:13px;border-radius:6px;border:1px solid var(--ink);background:var(--ink);color:#fff;padding:9px 16px;cursor:pointer;font-weight:500;text-decoration:none;display:inline-block}
  a.button:hover,label.button:hover{background:var(--ink-2)}
  a.button.ghost,label.button.ghost,label.ghost{background:var(--panel);color:var(--ink);border-color:var(--line)}
  a.button.ghost:hover,label.button.ghost:hover,label.ghost:hover{background:var(--accent-soft);border-color:var(--accent-line)}
  a.accent,label.accent,a.button.accent,label.button.accent{background:var(--accent);border-color:var(--accent);color:#fff}
  /* The background is restated because a.button:hover above would otherwise
     darken an accent link to the plain button's hover colour. */
  a.accent:hover,label.accent:hover,a.button.accent:hover,label.button.accent:hover{background:var(--accent);filter:brightness(1.06)}
  label.sm{padding:7px 12px;font-size:12px}
  /* Item 49's trap, for the two classes it did not cover: the global
     a:visited/a:hover rules are element+pseudo-class, so only a class-only
     selector outranks them. .ghost:visited is restated last because BASE_CSS
     sets it earlier in the file and .button:visited would otherwise win the
     tie on source order for an <a class="button ghost">. */
  .button:visited{color:#fff}
  .accent:visited{color:#fff}
  .ghost:visited{color:var(--ink)}
  .button:hover,.accent:hover{text-decoration:none}
  /* A <button> and an <a class="ghost"> sat at different heights side by
     side: identical padding, but the button takes the UA's line-height while
     the anchor inherits body's 1.5, which is four pixels at 12px. Andy:
     "Exports button is taller than the update one - looks odd." Both are
     pinned here rather than one nudged to match the other, so they cannot
     drift apart again. */
  button,a.ghost,a.button,label.button,a.accent,label.accent,label.ghost{line-height:1.25}
  /* The advert count is a fact, not a control. Boxed in a panel with a
     border it read as a button sitting in a row of buttons - Andy: "not sure
     on the box around the adverts count as it makes it look like a button".
     It keeps the ink colour so it still reads as a value against the muted
     text around it. */
  .filebar .fname{background:none;border:0;padding:0;font-weight:600}
  /* The sellers list opens with a stat row and its two sync buttons rather
     than a filebar, and it is the same kind of thing: the section's headline
     numbers plus the controls that refresh them. Andy: "the top of the all
     sellers list page needs that dark cream top bar too - where the summary
     numbers are and buttons to bring it in line." Same band, more padding,
     because the figures here are 26px rather than 12px text. */
  .wrap > .statrow:first-child{
    margin:-22px -22px 16px;padding:14px 22px;
    background:#e3dbc7;border-bottom:1px solid #d3c9b0}

  /* A rule between two links that sit side by side, so they read as two
     choices rather than one phrase - and one of them undoes 152 decisions.
     A drawn line rather than a "|" character: it does not inherit the link
     underline, and it centres on the text properly at any size. */
  .pipe {
    display: inline-block; width: 1px; height: 11px;
    margin: 0 9px; vertical-align: -1px;
    background: var(--line, #d9d4c6);
  }

  /* ---- item 131 -------------------------------------------------------- */

  /* The intro belongs to the tab, so it is drawn as that tab's panel: the
     bar keeps its line, the panel hangs off it with no gap, and the two
     read as one control. Andy: "should the Tab intro text be attached to
     the tabs ... rather than being in a seperate box under it?" */
  .tabbar.joined { margin-bottom: 0; }
  .tabintro {
    margin: 0 0 18px; padding: 11px 15px;
    background: var(--panel, #fff);
    border: 1px solid var(--line, #ddd8c8); border-top: none;
    border-radius: 0 0 7px 7px;
    font-size: 12.5px; line-height: 1.55; color: var(--muted, #6c7169);
  }

  /* The two lines under the search bar say what is in the list. They were
     sitting hard against the approve buttons, which put "undo them" a few
     pixels from "Approve all 16,939" and made both read as one group.
     Andy: "do you think the already decided show and undo are in the right
     place?" - the right place, the wrong distance. */
  .groupstats.quiet { margin-bottom: 16px; }
  .filterwhy + .groupstats.quiet { margin-top: 4px; }
  .bulkbar { margin-top: 16px; }

  /* ---- item 130, third pass: one bar ---------------------------------- */

  /* The counts move into the search bar, at its right hand end, because
     they are what that bar has narrowed the list down to. Andy: "the
     numbers should go above the search bar or inside it as this shows what
     you will be searching and filtering on." Inside, and they push right so
     the controls stay grouped on the left. */
  .filterbar .barcounts {
    margin-left: auto; font-family: var(--mono); font-size: 12.5px;
    color: var(--ink, #212423); white-space: nowrap;
  }
  .filterbar .barcounts b { color: var(--accent, #1f6f4f); font-weight: 600; }
  .filterbar .barcounts .sep { margin: 0 9px; color: var(--soft, #a2a294); }
  /* Below the bar the counts would be a wall of mono, so let them wrap and
     drop the auto-margin that was pinning them to the right. */
  @media (max-width: 1080px) {
    .filterbar .barcounts { margin-left: 0; white-space: normal; flex: 1 1 100%; }
  }

  /* What is left under the bar is a note, not a headline: rows already
     decided here, and how to see or undo them. */
  .groupstats.quiet {
    margin-top: 8px; padding: 7px 12px;
    background: none; border: none;
    font-size: 12px; color: var(--muted, #6c7169);
  }
  .groupstats.quiet b { color: var(--ink, #212423); }

  /* ---- item 130, second pass ---------------------------------------- */

  /* BASE_CSS gives every <details> a border, radius and padding, and
     summary.toggle carries its own - so the menu was drawing a box inside a
     box. Andy: "The filter button as a second box around it." The outer one
     goes; the button keeps its. */
  details.queuefilters {
    border: none; padding: 0; margin: 0; background: none;
  }

  /* Same grey as the search magnifier (.filterbar .searchwrap svg), so the
     two filter controls on a page read as the same kind of thing. It takes
     the accent when a filter is on, with the rest of the chip. */
  summary.toggle .ficon {
    color: var(--soft, #a2a294);
    vertical-align: -2px; margin-right: 5px;
  }
  summary.toggle.on .ficon { color: var(--accent, #1f6f4f); }

  /* What the current filter means, directly under the button that names it.
     Quiet: this replaced .filterchip's warn-coloured border, which read as
     an error beside a control somebody had just used on purpose. */
  .filterwhy {
    display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
    margin: 7px 0 0; padding: 0 2px;
    font-size: 12px; color: var(--muted, #6c7169);
  }
  .filterwhy span { flex: 1 1 320px; }
  .filterwhy .clear { font-weight: 600; white-space: nowrap; }

  /* ---- item 130: the queue's rows and its filter menu ---------------- */

  /* The whole row toggles its own detail (CLEANUP_ROWCLICK_JS), so it has
     to look like it will. Controls inside keep their own cursor, and the
     expanded panel is left alone: text you might want to read and copy
     should not advertise itself as a button. */
  .cleanrow { cursor: pointer; }
  .cleanrow input, .cleanrow button, .cleanrow a,
  .cleanrow select, .cleanrow label, .cleanrow summary { cursor: auto; }
  .cleanrow button, .cleanrow a, .cleanrow summary,
  .cleanrow input[type=checkbox] { cursor: pointer; }
  .cleanrow details.mini[open] > div { cursor: auto; }

  /* Eight filters behind one control. Anchored to the summary so the panel
     opens under the button rather than pushing the stats line apart. */
  details.queuefilters { position: relative; display: inline-block; }
  details.queuefilters > summary.toggle { white-space: nowrap; }
  .queuefilters-panel {
    position: absolute; z-index: 40; top: calc(100% + 6px); left: 0;
    min-width: 300px; padding: 5px; border-radius: 10px;
  }
  .queuefilters-panel .qfopt {
    display: flex; align-items: center; justify-content: space-between;
    gap: 18px; padding: 7px 10px; border-radius: 6px;
    color: var(--ink, #212423); text-decoration: none; font-size: 13px;
  }
  .queuefilters-panel .qfopt b {
    font-family: var(--mono); font-size: 12px; color: var(--muted, #6c7169);
  }
  .queuefilters-panel .qfopt:hover { background: rgba(31, 111, 79, .06); }
  /* The one that is on, and its count, both carry the accent - a tick would
     need a second glyph to say "and this is the number for it". */
  .queuefilters-panel .qfopt.on { background: var(--accent-soft, #e4f0ea); }
  .queuefilters-panel .qfopt.on,
  .queuefilters-panel .qfopt.on b { color: var(--accent, #1f6f4f); font-weight: 600; }
  /* Separates "everything" from the narrowings, since the first is the way
     out and the rest are ways in. */
  .queuefilters-panel .qfopt:first-child {
    border-bottom: 1px solid var(--line, #d9d4c6);
    border-radius: 6px 6px 0 0; margin-bottom: 3px; padding-bottom: 9px;
  }
  /* The stats row wraps, so on a narrow screen the button can end up close
     to the right edge with 300px of panel to its right. Anchor from the
     other side there rather than letting it hang off. */
  @media (max-width: 760px) {
    .queuefilters-panel { left: auto; right: 0; }
  }
  /* Item 136 - the product name beside a listing whose title differs from
     the group heading. Only ever rendered on a mismatch, so it is the
     difference itself that is on screen, not a repeat of the heading.
     Non-mono and indented: the Ad/stock/price part is a fixed-width row of
     figures and the name is prose, so setting them apart lets a stack of
     "2 x / 3 x / 4 x" read as a ladder at a glance. */
  .cleanrow .advname {
    font-family: var(--sans, system-ui, sans-serif);
    color: var(--ink, #2b2a26); font-size: 12.5px; margin-left: 10px;
  }
  .cleanrow .advname::before { content: "–"; margin-right: 7px;
    color: var(--muted, #7d7768); }

  /* Item 278: Settings, Errors and the worker. A traceback is long, so it
     sits behind its one-line summary; a worker process that never got as
     far as running is tinted like any other warning. */
  details.problem summary { cursor: pointer; }
  details.problem pre { white-space: pre-wrap; font-size: 11.5px; margin: 8px 0 0;
    max-height: 360px; overflow: auto; }
  tr.problem-row td { background: var(--warn-soft, #f8ecd6); }
  td.nowrap { white-space: nowrap; }

  /* Item 234 - notes as a thread. Yours on the right, everyone else's on
     the left; an edit sits in the middle because it is something that
     happened to the report rather than something somebody said. */
  .notelist{list-style:none;margin:0;padding:2px 0;display:flex;flex-direction:column;gap:10px}
  /* Item 244 guessed at a hairline landing between screen pixels and gave
     this a darker edge; item 245 found the real cause (a stale rule, at the
     foot of this file), so the edge goes back to the line colour every
     other panel uses. */
  .notelist .bubble{max-width:78%;align-self:flex-start;background:var(--panel,#f2efe6);
    border:1px solid var(--line);border-radius:12px 12px 12px 3px;padding:9px 12px}
  .notelist .bubble.mine{align-self:flex-end;background:var(--accent-soft,#e6f0ea);
    border-color:var(--accent-line,#bcd8c8);border-radius:12px 12px 3px 12px}
  /* Item 243 - Andy: *"could it have a green border when clicked instead of
     blue and rounded corners to match our other apple like styling?"*. The
     same treatment the bug report form has; a plain textarea was getting
     the browser's own square box and blue ring. */
  /* Item 244 - Andy: *"The bottom text box hits the last bubble in the
     chat - needs some margin"*. It had one until the form took a class
     instead of a style attribute. */
  .notesend{margin-top:16px}
  .notesend textarea{width:100%;box-sizing:border-box;padding:10px 13px;
    border:1px solid var(--line);border-radius:12px;background:#fff;font:inherit;
    font-size:14px;line-height:1.5;color:var(--ink);resize:vertical}
  .notesend textarea::placeholder{color:var(--soft,#9a958c)}
  .notesend textarea:focus{outline:none;border-color:var(--accent-line);
    box-shadow:0 0 0 3px var(--accent-soft)}
  .sendrow{margin-top:8px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
  .sendhint{font-size:11.5px;color:var(--muted)}
  .notelist .nonote{align-self:flex-start;font-size:13px}
  .notelist .bwho{font-size:11px;color:var(--muted);margin-bottom:3px}
  .notelist .btext{font-size:13.5px;line-height:1.55;color:var(--ink)}
  .notelist .noteedit{align-self:center;max-width:84%;text-align:center;font-size:12px}
  .notelist .noteedit .wasnote{margin-top:4px;padding:7px 11px;border-radius:9px;
    border:1px dashed var(--line);color:var(--muted);text-align:left;font-size:12.5px;
    line-height:1.5}
  /* A row of fields with a button on the end. label.fld carries a 5px
     bottom margin and flex-end aligns margin boxes, so `row` put the
     button 5px below the fields (item 234). */
  .formrow{display:flex;gap:10px;align-items:flex-end;margin-top:14px;flex-wrap:wrap}
  .formrow > label.fld{margin-bottom:0}
  /* Item 292 - editing is a page of its own, so the <details> this used to
     open has gone, and with it item 244's hand-rolled copy of the button
     look. A <summary> is neither `button` nor `a`, so `class="ghost sm"`
     drew as plain text and the whole box had to be spelled out again here.
     A link is an `a`, and item 286's rules already cover it. Three of the
     five hand-rolled <summary> buttons in the handover's button sweep are
     left; this was the fourth. */
  .editrow{margin:14px 0 0}
  /* The edit page's Save and Cancel wear ui_theme's shared `.actionrow`
     since item 295, which is where this rule went. Cancel is a way back,
     which is what cancelling an edit is, so it is a link wearing the ghost
     button. */

  /* Bug reports and ideas. */
  .pill.pkind{background:var(--accent-soft);color:var(--accent)}
  .pill.pnew{background:#fdeccd;color:#7a4b00}
  .pill.ptodo{background:#efe6ff;color:#553b8f}
  .pill.pdoing{background:#d8e9ff;color:#13457f}
  .pill.pdone{background:#d7eede;color:#1f6b3e}
  .pill.pwont{background:var(--line);color:var(--muted)}
  .pill.pwithdrawn{background:var(--line);color:var(--muted)}
  .withdrawrow{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:12px;
    font-size:12px}
  .pill.phigh{background:#fadddd;color:#932020}
  .pill.pnormal{background:var(--panel);color:var(--muted);border:1px solid var(--line)}
  .pill.plow{background:var(--line);color:var(--muted)}
  /* Item 201 - the three priorities across the row menu, each in the colour
     the list already gives it, so the menu and the table agree on sight. */
  .prioset{display:flex;gap:6px;padding:2px 10px 8px}
  .prioset form{flex:1 1 0}
  .prioset .priopick{width:100%;border:1px solid transparent;cursor:pointer;
    font-size:11px;padding:5px 4px;text-align:center;font-family:var(--mono);
    letter-spacing:.03em}
  .prioset .priopick:hover{filter:brightness(.96)}
  /* The one it is on, marked rather than removed: a menu that hides the
     current value makes you deduce it from what is missing. */
  .prioset .priopick.on{outline:2px solid var(--accent);outline-offset:1px;
    font-weight:700;cursor:default}
  .reportdetails{margin:12px 0 0;font-size:13.5px;line-height:1.6}
  /* Item 282 - a description as the editor formats it (the same rules
     style the editor itself, so what you type is what is shown). */
  .richtext > :first-child{margin-top:0}
  .richtext > :last-child{margin-bottom:0}
  .richtext p{margin:0 0 10px;color:inherit}
  .reportdetails.richtext,.rtedit{color:var(--ink)}
  .richtext h2{font-size:18px;margin:16px 0 8px}
  .richtext h3{font-size:15.5px;margin:14px 0 6px}
  .richtext h4{font-size:13.5px;margin:12px 0 6px;text-transform:none;letter-spacing:0}
  .richtext ul,.richtext ol{margin:0 0 10px;padding-left:22px}
  .richtext li{margin:2px 0}
  .richtext img{display:block;max-width:100%;height:auto;margin:8px 0;
    border:1px solid var(--line);border-radius:6px}
  /* Item 282 - the editor (ui_theme.RICH_JS). Its toolbar is the Toolbox's
     own .ghost.sm buttons and a select; only the frame is new. */
  .rtwrap{position:relative;border:1px solid var(--line);border-radius:6px;background:#fff}
  .rtwrap:focus-within{outline:2px solid var(--accent-line);outline-offset:-1px}
  .rtbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:6px;
    border-bottom:1px solid var(--line);background:var(--panel);border-radius:6px 6px 0 0}
  .rtbar select.rtstyle{width:auto;padding:5px 8px;font-size:12.5px}
  /* Item 293 - Undo and Redo, which sit a little apart from the formatting
     buttons because they are not formatting. Greyed when there is nothing
     behind them; `.ghost` has no disabled state of its own. */
  .rtbar .rtstep:last-of-type{margin-right:6px}
  .rtbar .rtstep[disabled]{opacity:.45;cursor:default}
  .rtbar .rtstep[disabled]:hover{background:var(--panel);border-color:var(--line)}
  /* Item 293b - the icon buttons. `.ghost.sm` is shaped for a word, so an
     icon on its own needs the padding squared up and the glyph centred;
     everything else about the button is still the Toolbox's. The name is on
     the button's title and aria-label, not on the screen. */
  .rtbar .rticon{padding:6px 8px;line-height:0}
  .rtbar .rticon svg{display:block;width:15px;height:15px}
  .rtlink{display:flex;gap:6px;align-items:center;padding:6px;border-bottom:1px solid var(--line)}
  .rtlink[hidden]{display:none}
  .rtlink input{flex:1;min-width:0}
  .rtedit{position:relative;min-height:130px;max-height:50vh;overflow:auto;padding:10px 12px;
    font-size:13.5px;line-height:1.6;outline:none}
  .rtwrap.isempty .rtedit::before{content:attr(data-placeholder);position:absolute;
    left:12px;top:10px;right:12px;color:var(--soft);pointer-events:none}
  .rtnote{padding:6px 12px;font-size:12px;color:var(--warn,#9a5b00);border-top:1px solid var(--line)}
  .rtnote[hidden]{display:none}
  .factrow{display:grid;grid-template-columns:120px 1fr;gap:4px 14px;padding:5px 0;font-size:13px}
  .factrow .fact{font-family:var(--mono);font-size:10.5px;text-transform:uppercase;
    letter-spacing:.05em;color:var(--muted);padding-top:2px}
  .plainlist{list-style:none;margin:0;padding:0}
  /* Item 245 - Andy found what was cutting the first bubble's edge: notes
     used to be a plain list with a rule between them, and these two rules
     outlived it. `.notelist li:first-child` (0,2,1) outranks
     `.notelist .bubble` (0,2,0), so it went on taking the top border off
     the first note long after the first note became a bubble - and the
     `.notelist` half of the rule above was quietly resetting the list's
     own padding too. Nothing pale or half-pixel about it. */
  .ghost.sm.on{border-color:var(--accent-line);background:var(--accent-soft);color:var(--accent)}
  td.reportacts{white-space:nowrap;text-align:right;width:1%}
  /* Item 201 - clickable column headings. The whole cell is the target, not
     just the words, since a heading is a small thing to hit. */
  .reporttable th.sortable{padding:0}
  .reporttable th.sortable a{display:block;padding:9px 12px 9px 0;color:inherit;
    text-decoration:none;white-space:nowrap}
  .reporttable th.sortable a:hover{color:var(--accent);text-decoration:none}
  .reporttable th.sortable.on a{color:var(--accent)}
  .sortmark{margin-left:5px;font-size:11px}
  /* Item 246 - the comments column. */
  .reporttable th.cmthead{padding:9px 12px 9px 0;white-space:nowrap}
  .cmtcell{white-space:nowrap}
  /* Item 281 */
  .reporttable td.toolcell{font-size:12.5px;color:var(--muted);white-space:nowrap}
  .cmtn{font-family:var(--mono);font-size:12.5px;color:var(--muted)}
  .cmtbub{display:inline-block;min-width:18px;height:18px;padding:0 5px;border-radius:20px;
    background:var(--badge,#c0452f);color:#fff;font-family:var(--mono);font-size:10.5px;
    font-weight:600;line-height:18px;text-align:center}
  .cmtof{font-family:var(--mono);font-size:11px;color:var(--muted);margin-left:5px}
  /* Visible enough to say "this is a control", quiet enough not to compete
     with the heading or with the column actually being sorted on. */
  .sortmark.idle{opacity:.32}
  .reporttable th.sortable a:hover .sortmark.idle{opacity:.7}
  .rowmenu summary.dots{display:inline-flex;align-items:center;justify-content:center;
    width:32px;height:28px;padding:0;color:var(--muted)}
  .rowmenu summary.dots:hover{color:var(--accent)}
  details.rowmenu > .rowmenu-panel{width:186px;padding:5px;text-align:left}
  /* Small enough to sit beside its button at any width, so it keeps its
     place on a phone rather than taking the whole screen the way the
     schedule editor does. */
  @media (max-width:600px){
    details.rowmenu > .rowmenu-panel{position:absolute;left:auto;right:0;top:calc(100% + 8px);
      width:186px;max-height:none}
  }
  .rowmenu-head{font-family:var(--mono);font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;
    color:var(--muted);padding:5px 10px 6px}
  .rowmenu-panel form{margin:0}
  .rowitem{display:block;width:100%;text-align:left;background:none;border:none;border-radius:7px;
    padding:7px 10px;font:inherit;font-size:13.5px;color:var(--ink);cursor:pointer;text-decoration:none}
  .rowitem:hover{background:var(--accent-soft);color:var(--accent);text-decoration:none}
  .rowsep{height:1px;background:var(--line);margin:5px 6px}
  /* No scrolling box around this table: it would clip the row menu, as it
     did on the schedules table. Narrow screens drop the columns a pill
     already says, and the tool (item 281), which the search still finds. */
  @media (max-width:720px){
    .reporttable th:nth-child(1),.reporttable td:nth-child(1),
    .reporttable th:nth-child(3),.reporttable td:nth-child(3),
    .reporttable th:nth-child(4),.reporttable td:nth-child(4){display:none}
    /* The count goes with them; the mark that there is something new for
       you does not, since that is the reason to open the thing. */
    .cmtof{display:none}
  }

/* The Trustpilot check (item 285). */
/* ui_theme's own .drop, as the Import Builder uses it. Only the dragged-over
   state and the keyboard focus ring are added. */
#tp-file .drop.over{background:#dcebe3;border-color:var(--accent)}
#tp-file .drop:focus-visible{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-soft)}
#tp-file input[type=file]{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.fileinfo[hidden]{display:none}
.fileinfo{display:flex;flex-wrap:wrap;gap:10px 18px;align-items:center;
  padding:12px 14px;border:1px solid var(--line);border-radius:10px;background:var(--panel)}
.fileinfo b{font-size:15px}
.cmaps{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px 20px}
.cmap select{width:100%}
.cmap .opt{font-weight:400;color:var(--muted);font-size:12px}
.cmap.missing select{border-color:var(--warn);background:var(--warn-soft)}
/* Item 285b, Mike: the days are the first thing he sets, so they are the
   whole of their own step, and the boxes are the Toolbox's own shape rather
   than the browser's default. */
.dates{display:flex;flex-wrap:wrap;gap:14px 22px;align-items:flex-end;margin:4px 0 10px}
.datefield{display:flex;flex-direction:column;gap:7px}
.dates input[type=date]{font-family:var(--sans);font-size:16px;padding:11px 14px;
  border:1px solid var(--line);border-radius:10px;background:var(--paper);color:var(--ink);
  min-width:200px;line-height:1.2;transition:border-color .12s,box-shadow .12s}
.dates input[type=date]:hover{border-color:var(--accent-line)}
.dates input[type=date]:focus{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-soft)}
.dates input[type=date]::-webkit-calendar-picker-indicator{cursor:pointer;opacity:.55;
  padding:3px;border-radius:5px}
.dates input[type=date]::-webkit-calendar-picker-indicator:hover{opacity:1;
  background:var(--accent-soft)}
#sentnote .note{margin:0 0 14px}
#backnote .note{margin:0 0 12px}
#sentnote .forget{margin-left:8px}
/* The two settings, each a line saying what it is with its controls a click
   away (Mike: they are the same nearly every run). */
.tpsettings{padding-top:10px;padding-bottom:10px}
.setrow{display:flex;gap:14px;align-items:center;justify-content:space-between;
  padding:11px 0;border-top:1px solid var(--line)}
.setrow:first-child{border-top:0}
.setline{min-width:0}
.setline b{font-size:14px}
.setline .fine{display:inline;margin:0}
.setline .adrift{color:var(--warn)}
.tppanel{width:420px}
/* The close button is the pale one Settings uses, not the dark default a
   bare <button> falls back to outside that page's own scope. */
.tppanel .afclose{width:32px;height:32px;padding:0;display:inline-flex;align-items:center;
  justify-content:center;background:var(--paper);color:var(--muted);border:1px solid var(--line);
  border-radius:8px}
.tppanel .afclose:hover{border-color:var(--accent);color:var(--accent)}
/* Once a file is in, the drop zone has done its job: its line takes over,
   with the way back to an empty screen beside it (285c, Mike). */
#tp-file.loaded .drop,#tp-file.loaded #keepnote{display:none}
.fileinfo #startagain{margin-left:auto}
.tppanel .afbody{display:block;padding:12px 18px 16px}
.tppanelbody{padding:12px 18px 16px}
.tppanelbody .cmaps{grid-template-columns:1fr}
#tp-columns .tppanel{width:560px}
#tp-columns .tppanelbody .cmaps{grid-template-columns:1fr 1fr}
.checks .chk{margin:10px 0}
.checks .chk .fine{margin:3px 0 0 24px}
.tppanelbody .chk{margin:12px 0}
.tppanelbody .chk .fine{margin:3px 0 0 24px}
#repeatdays{width:64px}
.prodrow{display:grid;grid-template-columns:minmax(200px,1.2fr) minmax(200px,1fr);
  gap:10px 16px;align-items:center;padding:10px 0;border-top:1px solid var(--line)}
.prodrow:first-child{border-top:0}
.prodrow .was{font-family:var(--mono);font-size:12.5px;overflow-wrap:anywhere}
.prodrow input{width:100%}
.namerow{display:grid;grid-template-columns:minmax(120px,1fr) minmax(180px,1.4fr) minmax(140px,1fr);
  gap:10px 16px;align-items:center;padding:10px 0;border-top:1px solid var(--line)}
.namerow:first-child{border-top:0}
.namerow .was{font-family:var(--mono);font-size:13px}
.namerow .was em{font-style:normal;color:var(--muted)}
.namerow .evidence{font-size:13px;color:var(--muted);overflow-wrap:anywhere}
.namerow input{width:100%}
.namerow .why{display:block;font-size:12px;color:var(--muted)}
#preview{overflow-x:auto;margin:14px 0}
#preview table{border-collapse:collapse;width:100%;font-size:13px}
#preview th,#preview td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--line);
  white-space:nowrap;max-width:420px;overflow:hidden;text-overflow:ellipsis}
#preview th{font-weight:600;color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.04em}
.outrow{display:flex;gap:14px;align-items:flex-end;flex-wrap:wrap}
@media (max-width:760px){
  .tppanel,#tp-columns .tppanel{width:auto}
  #tp-columns .tppanelbody .cmaps{grid-template-columns:1fr}
  .setrow{flex-wrap:wrap}
}


  /* The tools menu: hamburger, slide-out panel, dimmed and blurred page. */
  header.top .menubtn{display:inline-flex;align-items:center;justify-content:center;
    width:44px;height:44px;border-radius:10px;color:#fff;margin-right:2px;flex:none;
    background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.22)}
  header.top .menubtn:hover{background:rgba(255,255,255,.22);text-decoration:none}
  header.top .menubtn:focus-visible{outline:2px solid #fff;outline-offset:2px}
  .drawerscrim{position:fixed;inset:0;z-index:900;background:rgba(18,24,21,.38);
    -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
    opacity:0;pointer-events:none;transition:opacity .2s ease}
  .drawer{position:fixed;top:0;left:0;bottom:0;z-index:901;width:min(380px,88vw);
    background:var(--panel);box-shadow:12px 0 40px rgba(0,0,0,.18);
    display:flex;flex-direction:column;transform:translateX(-100%);visibility:hidden;
    transition:transform .22s cubic-bezier(.2,.8,.2,1),visibility 0s linear .22s}
  body.drawer-open{overflow:hidden}
  body.drawer-open .drawerscrim{opacity:1;pointer-events:auto}
  body.drawer-open .drawer{transform:none;visibility:visible;
    transition:transform .22s cubic-bezier(.2,.8,.2,1)}
  .drawerhead{display:flex;align-items:center;justify-content:space-between;
    padding:18px 18px 12px 22px;background:linear-gradient(105deg,var(--brand),var(--brand-2));color:#fff}
  .drawerhead h2{margin:0;font-size:13px;font-family:var(--mono);font-weight:600;
    letter-spacing:.08em;text-transform:uppercase}
  .drawerclose{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;
    border-radius:10px;border:1px solid rgba(255,255,255,.25);background:rgba(255,255,255,.1);
    color:#fff;cursor:pointer;padding:0}
  .drawerclose svg,header.top .menubtn svg{flex:0 0 auto}
  .drawerclose:hover{background:rgba(255,255,255,.22)}
  .drawerlist{overflow-y:auto;padding:10px 12px 24px}
  .toolitem{display:flex;gap:14px;align-items:flex-start;padding:10px 12px;border-radius:10px;
    color:var(--ink);text-decoration:none;border:1px solid transparent}
  a.toolitem:hover{background:var(--accent-soft);border-color:var(--accent-line);text-decoration:none}
  .toolitem .ico{flex:0 0 36px;height:36px;border-radius:10px;display:flex;align-items:center;
    justify-content:center;background:var(--accent-soft);color:var(--accent)}
  .toolitem .txt{display:flex;flex-direction:column;gap:2px;min-width:0}
  .toolitem b{font-size:14.5px;font-weight:600}
  .toolitem small{font-size:12.5px;color:var(--muted);line-height:1.35}
  /* A tool not built yet needs its name and label, not its description; nor
     does a developer's own tool, in the menu (Andy, 12 September). */
  .toolitem.planned small{display:none}
  .drawerlist .toolitem.quiet{align-items:center}
  .drawerlist .toolitem.quiet small{display:none}
  /* How many are waiting, on the tool's icon. */
  .toolitem .ico{position:relative}
  .toolitem .bub{position:absolute;top:-6px;right:-7px;min-width:18px;height:18px;padding:0 5px;
    border-radius:20px;background:var(--badge,#c0452f);color:#fff;font-family:var(--mono);
    font-size:10.5px;font-weight:600;display:flex;align-items:center;justify-content:center;
    box-shadow:0 0 0 2px var(--panel)}
  .toolitem em{font-style:normal;font-family:var(--mono);font-size:10px;letter-spacing:.06em;
    text-transform:uppercase;color:var(--soft);margin-top:4px}
  .toolitem.current{background:var(--paper);border-color:var(--line)}
  .toolitem.current em{color:var(--accent)}
  .toolitem.planned{opacity:.55}
  .toolitem.planned .ico{background:transparent;border:1px dashed var(--soft);color:var(--soft)}
  .toolitem b .away{margin-left:6px;color:var(--muted);vertical-align:-1px}
  .toolitem.away:hover b .away{color:var(--accent)}
  .drawerlist{flex:1 1 auto}
  .drawerfoot{border-top:1px solid var(--line);padding:10px 12px 14px;background:var(--panel)}
  .settingsitem .ico{background:transparent;border:1px solid var(--line);color:var(--ink-2)}
  /* Help, What's new and Report a bug, at the foot of the menu (Andy, 12
     September). Buttons, not links: they open a pop-up rather than go
     anywhere, but they read as the same rows. */
  /* The foot of the menu is one line a row, so the tools above it stay on
     screen (Andy, 12 September). */
  .drawerfoot{display:flex;flex-direction:column;gap:1px}
  .drawerfoot .toolitem{padding:7px 12px;gap:11px;align-items:center}
  .drawerfoot .toolitem .ico{flex:0 0 30px;height:30px;border-radius:8px}
  .drawerfoot .toolitem .ico svg{width:17px;height:17px}
  .drawerfoot .toolitem b{font-size:13.5px;font-weight:600}
  .drawerfoot .toolitem small{display:none}
  button.toolitem.menuaction{width:100%;text-align:left;background:none;font:inherit;cursor:pointer}
  button.toolitem.menuaction:hover{background:var(--accent-soft);border-color:var(--accent-line)}
  .menuaction .ico{background:transparent;border:1px solid var(--line);color:var(--ink-2)}
  .menuaction:hover .ico{border-color:var(--accent-line);color:var(--accent)}
  .toolspage{max-width:520px}
  /* The settings page. */
  .settingsgrid > .card{margin:0 0 14px}
  /* Jump links along the top of a long page of sections. */
  .jumpbar{display:flex;gap:4px;flex-wrap:wrap;margin:0 0 16px;padding:6px 0 8px;
    border-bottom:1px solid var(--line);background:var(--bg,#efe9da);
    position:sticky;top:var(--chrome-h,var(--hdr-h,0px));z-index:20}
  .jumpbar .jump{padding:7px 12px;border-radius:20px;font-size:12.5px;color:var(--muted);
    text-decoration:none;border:1px solid transparent;white-space:nowrap}
  .jumpbar .jump:hover{color:var(--ink);background:var(--panel);text-decoration:none}
  .jumpbar .jump.on{color:var(--accent);background:var(--accent-soft);border-color:var(--accent-line);
    font-weight:600}
  /* Item 197 - a jump link that leaves the page. Marked with an arrow so it
     is not mistaken for one that scrolls, and it never takes .on because
     there is no section below it to be inside. */
  .jumpbar .jump.away::after{content:" →";opacity:.5}
  .jumpbar .jump.away{border-color:var(--line)}
  @media (max-width:600px){
    .jumpbar{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch}
  }
  /* A tool filling the window below the header (page(full=True)). */
  body.fullpage{height:100vh;display:flex;flex-direction:column;overflow:hidden}
  body.fullpage > header.top{flex:none}
  body.fullpage > .partsbar{flex:none}
  body.fullpage > .wrap{flex:1 1 auto;display:flex;max-width:none;margin:0;padding:0;min-height:0}
  body.fullpage iframe.toolframe{flex:1 1 auto;width:100%;border:0;display:block;background:#efe9da}
  .settingsrow{display:flex;flex-wrap:wrap;gap:14px;margin-bottom:14px}
  .settingsrow > .card{flex:1 1 300px;min-width:0;margin:0}
  /* Item 304. A column in a settings row holding two short cards instead of
     one tall one. Sized exactly like a card so the row still divides evenly,
     and its own cards lose their margins the way a direct child does - the
     rule above cannot reach them, because they are grandchildren. */
  .settingsstack{display:flex;flex-direction:column;gap:14px;flex:1 1 300px;min-width:0}
  .settingsstack > .card{margin:0}
  .avatar{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:50%;
    background:var(--accent,#1f6f4f);color:#fff;font-weight:600;font-size:15px;letter-spacing:.03em;flex:none}
  .avatar.sm{width:30px;height:30px;font-size:11.5px;background:var(--accent-soft);color:var(--accent,#1f6f4f)}
  .profilehead{display:flex;align-items:center;gap:12px;margin:4px 0 14px}
  .userstable td{vertical-align:middle}
  .userstable td small{display:block}
  .usersadd{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px;align-items:center}
  .usersadd input{flex:1 1 180px;min-width:0}
  .usersadd select{flex:0 0 auto;width:auto}
  button.switch:disabled{cursor:default;opacity:.55}
  /* Scheduled updates (app.schedules_card). */
  /* No overflow box around the table: it would clip the Change panel. On a
     narrow screen the rows stack instead of scrolling sideways. */
  .schedcard .tablewrap{margin-top:12px}
  .schedtable{width:100%;border-collapse:collapse;font-size:13px}
  .schedtable th{font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.06em;
    text-transform:uppercase;color:var(--muted);text-align:left;padding:10px 12px 9px 0;border-bottom:1px solid var(--line)}
  /* The cream header band needs the same inset as a row, left and right
     (Andy: "needs some more padding at the top"). */
  .schedtable th:first-child,.schedtable td:first-child{padding-left:12px}
  .schedtable th:last-child,.schedtable td:last-child{padding-right:12px}
  .schedtable thead th:first-child{border-top-left-radius:6px}
  .schedtable thead th:last-child{border-top-right-radius:6px}
  .schedtable td{padding:12px 12px 12px 0;border-bottom:1px solid var(--line);vertical-align:top}
  .schedtable tr:last-child td{border-bottom:none}
  .schedtable td small{display:block;color:var(--muted);font-size:12px;line-height:1.4;margin-top:2px;max-width:360px}
  .schedtable td small.schedfor{color:var(--ink-2,#33403a);margin-top:4px}
  /* A switched-off row fades, but `opacity` applies to a whole subtree as a
     group and nothing inside can undo it - so the Update cell fades its own
     text instead of fading the cell, and the pop-up hanging off it stays
     readable when it is opened. Its opener fades with the words around it;
     the panel does not, the same way .schedacts (Change, Run now) has never
     faded, because a control you can still use has to stay legible. */
  .schedtable tr.off td:not(.schedon):not(.schedacts):not(.schedupdate){opacity:.62}
  .schedtable tr.off td.schedupdate > b,
  .schedtable tr.off td.schedupdate > small{opacity:.62}
  .schedtable tr.off details.schedwho > summary{opacity:.62}
  .schedtable tr td{transition:opacity .2s ease}
  .schedtable .nowrap{white-space:nowrap}
  .schedtable form.inline{display:inline;margin:0}
  .schedacts{white-space:nowrap;text-align:right}
  .schedacts > *{vertical-align:middle}
  .schedacts form.inline{margin-left:6px}
  .schedstate{font-weight:600}
  .schedstate.ok{color:var(--accent,#1f6f4f)}
  .schedstate.bad{color:#b3261e}
  .schedstate.warn{color:#8a5a00;font-weight:500}
  .schedstate.run{color:var(--ink-2,#33403a)}
  /* An on/off switch, the Apple way: a pill whose knob slides across. */
  button.switch{position:relative;width:42px;height:25px;padding:0;border-radius:999px;border:none;
    background:#d9d3c5;cursor:pointer;transition:background .2s ease;flex:none;margin-top:1px}
  button.switch span{position:absolute;top:2px;left:2px;width:21px;height:21px;border-radius:50%;background:#fff;
    box-shadow:0 1px 3px rgba(0,0,0,.22),0 0 0 .5px rgba(0,0,0,.05);transition:transform .22s cubic-bezier(.2,.8,.2,1)}
  button.switch.on{background:var(--accent,#1f6f4f)}
  button.switch.on span{transform:translateX(17px)}
  button.switch:focus-visible{outline:2px solid var(--accent,#1f6f4f);outline-offset:2px}
  details.schedit{display:inline-block;position:relative;border:none;padding:0;margin:0;background:none}
  details.schedit > summary{list-style:none;cursor:pointer;display:inline-block;font-family:var(--sans);
    font-size:12px;font-weight:500;line-height:1.25;border-radius:6px;border:1px solid var(--line);
    background:var(--panel);color:var(--ink);padding:7px 12px}
  details.schedit > summary:hover,details.schedit[open] > summary{background:var(--accent-soft);border-color:var(--accent-line)}
  details.schedit > summary::-webkit-details-marker{display:none}
  .menubtns{display:flex;flex-wrap:wrap;align-items:center}
  .schedit-panel{position:absolute;z-index:61;top:calc(100% + 8px);right:0;width:380px;text-align:left;white-space:normal}
  .schedform{margin:0}
  .schedgrid{display:grid;grid-template-columns:1fr 1fr;gap:12px 14px;padding:14px 18px 4px}
  .schedfield{display:flex;flex-direction:column;gap:5px;font-size:13px}
  .schedfield[hidden]{display:none}
  .schedfield.wide{padding:10px 18px 0}
  .schedlabel{font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
  .schedfield select,.schedfield input[type=time],.schedfind{width:100%;box-sizing:border-box}
  .schedfield input[type=time],.schedfind{padding:8px 10px;border:1px solid var(--line);border-radius:8px;
    background:#fff;font-family:var(--sans);font-size:13px;color:var(--ink)}
  .schedfield input[type=time]:focus,.schedfind:focus{outline:none;border-color:var(--accent-line);box-shadow:0 0 0 3px var(--accent-soft)}
  .schedsellers{display:flex;flex-direction:column;gap:7px;max-height:210px;overflow:auto;padding:8px 2px 4px}
  .schedsellers .afcheck[hidden]{display:none}
  .schedsellers em{font-style:normal;font-family:var(--mono);font-size:10px;color:var(--soft,#a2a294);text-transform:uppercase;letter-spacing:.05em}
  .schedhint{margin:8px 18px 12px;font-size:12px;color:var(--muted);line-height:1.45}
  .schedfield.wide .schedhint{margin:6px 0 0}
  /* Item 311 (report #40): who the nightly price check is for. The opener is
     a text link, not a button - a <summary> gets nothing from .ghost.sm and
     its box is already hand-rolled in three places (CLAUDE.md), and this line
     is a row of facts rather than a control. Everything below the summary is
     the pop-over machinery details.schedit already uses. */
  details.schedwho{display:inline-block;position:relative;border:none;padding:0;
    margin:2px 0 0;background:none}
  details.schedwho > summary{list-style:none;cursor:pointer;display:inline;padding:0;
    color:var(--accent,#1f6f4f);text-decoration:underline;text-underline-offset:2px}
  details.schedwho > summary::-webkit-details-marker{display:none}
  details.schedwho > summary:hover{text-decoration-thickness:2px}
  details.schedwho > summary:focus-visible{outline:2px solid var(--accent,#1f6f4f);
    outline-offset:2px;border-radius:4px}
  .schedwho-panel{position:absolute;z-index:61;top:calc(100% + 8px);left:0;width:440px;
    text-align:left;white-space:normal;color:var(--ink);font-size:13px}
  /* .afbody is the filter panel's two-column grid, so a table and the
     lines under it land side by side in it. .tppanel does the same thing
     for the same reason; only a render shows it, because two columns of
     the right words look deliberate. */
  .schedwho-panel .afbody{display:block;padding:2px 18px 6px}
  .schedwho-panel .schedhint{margin:10px 0 0}
  .whohead,.whorow{display:grid;grid-template-columns:1fr auto;gap:4px 14px;align-items:baseline}
  .whohead{font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.06em;
    text-transform:uppercase;color:var(--muted);padding:6px 0 4px}
  .whorow{padding:9px 0;border-top:1px solid var(--line);font-size:13px}
  .whowhen{white-space:nowrap}
  .whorow em{display:block;font-style:normal;font-family:var(--mono);font-size:10px;
    color:#8a5a00;text-transform:uppercase;letter-spacing:.05em;margin-top:3px}
  .settingsgrid .afclose{width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;
    border-radius:8px;border:1px solid var(--line);background:#fff;color:var(--ink-2,#33403a);cursor:pointer}
  .settingsgrid .afclose:hover{border-color:var(--accent,#1f6f4f);color:var(--accent,#1f6f4f)}
  @media (max-width:760px){
    .schedtable thead{display:none}
    .schedtable,.schedtable tbody,.schedtable tr,.schedtable td{display:block;width:auto}
    .schedtable tr{position:relative;padding:12px 0 12px 56px;border-bottom:1px solid var(--line)}
    .schedtable tr:last-child{border-bottom:none}
    .schedtable td{border:none;padding:2px 0}
    .schedtable td.schedon{position:absolute;left:0;top:12px}
    .schedtable td.nowrap::before{content:attr(data-label) " ";color:var(--muted)}
    .schedacts{text-align:left;padding-top:8px !important}
    .schedacts form.inline{margin-left:6px}
  }
  @media (max-width:600px){
    .schedit-panel,.schedwho-panel{position:fixed;left:12px;right:12px;top:12px;width:auto;max-height:calc(100vh - 24px);overflow:auto}
    /* One column on a phone: the state reads as a sentence on its own
       ("Done yesterday 02:06"), so the heading has nothing left to say. */
    .whohead{display:none}
    .whorow{grid-template-columns:1fr}
  }
  @media (prefers-reduced-motion: reduce){ button.switch,button.switch span{transition:none} }
  ul.plain{list-style:none;padding:0;margin:0 0 10px}
  ul.plain li{padding:4px 0;border-bottom:1px solid var(--line)}
  dl.facts{display:grid;grid-template-columns:auto 1fr;gap:6px 16px;margin:0;font-size:13px}
  dl.facts dt{color:var(--muted)}
  dl.facts dd{margin:0;font-family:var(--mono);font-size:12.5px}
  .soon{font-family:var(--mono);font-size:10px;letter-spacing:.06em;text-transform:uppercase;
    color:var(--soft);border:1px dashed var(--soft);border-radius:20px;padding:1px 8px;margin-left:6px;
    vertical-align:middle}
  .toolspage .toolitem{background:var(--paper);border-color:var(--line);margin-bottom:8px}
  .toolspage .toolitem.planned{background:transparent}
  @media (prefers-reduced-motion: reduce){
    .drawer,.drawerscrim,body.drawer-open .drawer{transition:none}
  }


  /* ---- motion: pop-overs and modals (MOTION_JS) --------------------- */
  @keyframes afIn { from { opacity: 0; transform: translateY(-6px) scale(.985); } }
  @keyframes afOut { to { opacity: 0; transform: translateY(-4px) scale(.99); } }
  @keyframes tbFade { from { opacity: 0; } }
  @keyframes tbFadeOut { to { opacity: 0; } }
  @keyframes tbRise { from { opacity: 0; transform: translateY(10px) scale(.97); } }
  @keyframes tbSink { to { opacity: 0; transform: translateY(6px) scale(.98); } }

  /* The shared look: frosted, faint two-layer shadow. */
  .popover-panel {
    background: rgba(252, 248, 243, .94);
    -webkit-backdrop-filter: blur(14px) saturate(1.2); backdrop-filter: blur(14px) saturate(1.2);
    border: 1px solid rgba(0, 0, 0, .07); border-radius: 12px;
    box-shadow: 0 1px 2px rgba(20, 24, 22, .05), 0 10px 30px rgba(20, 24, 22, .10);
  }
  details[data-popover] > .popover-panel { animation: afIn .16s cubic-bezier(.2, .8, .2, 1); }
  details[data-popover][data-restored] > .popover-panel { animation: none; }
  details[data-popover].closing > .popover-panel { animation: afOut .12s ease-in forwards; }

  /* Modals: the page behind fades and blurs, the box rises into place. */
  .modal-overlay { -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
  .modal-overlay.show { animation: tbFade .18s ease-out; }
  .modal-overlay.show .modal { animation: tbRise .22s cubic-bezier(.2, .8, .2, 1); }
  .modal-overlay.closing { animation: tbFadeOut .14s ease-in forwards; }
  .modal-overlay.closing .modal { animation: tbSink .14s ease-in forwards; }
  .modal { box-shadow: 0 1px 3px rgba(0, 32, 18, .06), 0 20px 50px rgba(0, 32, 18, .16); }
  .runmodal-backdrop { -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
  .runmodal:not([hidden]) .runmodal-backdrop { animation: tbFade .18s ease-out; }
  .runmodal:not([hidden]) .runmodal-panel { animation: tbRise .22s cubic-bezier(.2, .8, .2, 1); }
  .runmodal.closing .runmodal-backdrop { animation: tbFadeOut .14s ease-in forwards; }
  .runmodal.closing .runmodal-panel { animation: tbSink .14s ease-in forwards; }
  .runmodal-panel { box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 20px 50px rgba(0, 0, 0, .16); }

  /* Icon-only toolbar button (Customise columns). */
  summary.toggle.iconbtn { padding: 8px 11px; display: inline-flex; align-items: center; }
  summary.toggle.iconbtn svg { color: var(--muted, #6c7169); }
  summary.toggle.iconbtn.on svg { color: var(--accent, #1f6f4f); }

  /* Customise columns. */
  details.colpicker { position: relative; border: none; padding: 0; margin: 0; background: none; }
  .colpicker-panel { position: absolute; z-index: 61; top: calc(100% + 8px); right: 0; width: 340px; }
  .colhelp { margin: 10px 18px 6px; font-size: 12.5px; color: var(--muted, #6c7169); line-height: 1.45; }
  .collist { list-style: none; margin: 0; padding: 4px 10px 10px; overflow: auto; }
  /* Panels held to the window (MOTION_JS fit()): head and foot fixed, the
     middle scrolls. */
  .popover-panel.fit { display: flex; flex-direction: column; overflow: hidden; }
  .popover-panel.fit > .afhead, .popover-panel.fit > .affoot, .popover-panel.fit > .colhelp { flex: none; }
  .popover-panel.fit > .collist, .popover-panel.fit > .afbody { flex: 1 1 auto; min-height: 0; overflow: auto; }
  .popover-panel.fit > form { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
  .popover-panel.fit > form > .afbody, .popover-panel.fit > form > .schedbody { flex: 1 1 auto; min-height: 0; overflow: auto; }
  .popover-panel.fit > form > .affoot { flex: none; }
  .colrow { display: flex; align-items: center; gap: 10px; padding: 7px 8px; border-radius: 8px;
    background: transparent; position: relative; }
  .colrow:hover { background: rgba(31, 111, 79, .06); }
  .colrow.dragging { background: #fff; box-shadow: 0 6px 18px rgba(20, 24, 22, .14); opacity: .95; }
  .colrow .grip { width: 14px; display: inline-flex; justify-content: center; color: var(--soft, #a2a294);
    cursor: grab; }
  .colrow.locked .grip { cursor: default; }
  .colrow label { flex: 1 1 auto; display: flex; align-items: center; gap: 9px; font-size: 13px; cursor: pointer; }
  .colrow input[type=checkbox] { width: 16px; height: 16px; margin: 0; accent-color: var(--accent, #1f6f4f); }
  .colrow em { font-style: normal; font-family: var(--mono); font-size: 10px; letter-spacing: .05em;
    text-transform: uppercase; color: var(--soft, #a2a294); }
  .colrow .moves { display: inline-flex; gap: 4px; }
  .filterbar .colrow .mv, .sortlist .colrow .mv { width: 30px; height: 28px; padding: 0; border-radius: 7px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--line, #e7dcc4); background: #fff; color: var(--ink, #1d2420);
    cursor: pointer; transition: border-color .15s, color .15s, background .15s; }
  .sortlist .colrow .mv:hover { border-color: var(--accent, #1f6f4f); color: var(--accent, #1f6f4f); }
  .sortlist { list-style: none; margin: 8px 0 0; padding: 0; max-width: 480px; }
  .sortlist .colrow { border: 1px solid var(--line); background: var(--panel); margin-bottom: 6px; padding: 8px 10px; }
  .sortlist .colrow .ico { flex: 0 0 30px; height: 30px; border-radius: 8px; display: flex; align-items: center;
    justify-content: center; background: var(--accent-soft); color: var(--accent); }
  .sortlist .colrow .ico svg { width: 18px; height: 18px; }
  .sortlist .colrow .nm { flex: 1 1 auto; font-size: 13.5px; font-weight: 500; }
  .sortlist .colrow.planned .nm { color: var(--muted); }
  /* Hiding a tool from your own menu: the row stays, dimmed, with the eye
     closed, so it can be brought back. */
  .sortlist .colrow .eye { width: 30px; height: 28px; padding: 0; border-radius: 7px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--line); background: #fff; color: var(--muted); cursor: pointer; }
  .sortlist .colrow .eye:hover { border-color: var(--accent); color: var(--accent); }
  .sortlist .colrow.hiddenrow { opacity: .55; }
  .sortlist .colrow.hiddenrow .eye { color: var(--soft); }
  .sortlist .colrow .eye .shut, .sortlist .colrow.hiddenrow .eye .open { display: none; }
  .sortlist .colrow.hiddenrow .eye .shut { display: block; }
  .sortlist .nm em, .afcheck em.soonish { font-style: normal; font-family: var(--mono); font-size: 10px;
    letter-spacing: .05em; text-transform: uppercase; color: var(--soft, #a2a294); }
  .sortlist .nm em { display: block; margin-top: 1px; }
  .filterbar .colrow .mv:active { background: rgba(31, 111, 79, .08); }
  .colrow:first-child .mv.up, .colrow:last-child .mv.down { opacity: .35; }
  .filterbar .colrow .mv:hover { border-color: var(--accent, #1f6f4f); color: var(--accent, #1f6f4f); }
  .affoot .footbtns { display: inline-flex; gap: 8px; }
  .filterbar .affoot button.sm { padding: 6px 14px; }
  .filterbar .linkbtn { background: none; border: none; padding: 0; color: var(--muted, #6c7169);
    font-size: 12.5px; text-decoration: underline; cursor: pointer; }
  .filterbar .linkbtn:hover { color: var(--accent, #1f6f4f); }

  @media (prefers-reduced-motion: reduce) {
    details[data-popover] > .popover-panel, details[data-popover].closing > .popover-panel,
    .modal-overlay.show, .modal-overlay.show .modal, .modal-overlay.closing, .modal-overlay.closing .modal,
    .runmodal .runmodal-backdrop, .runmodal .runmodal-panel { animation: none !important; }
  }


  /* The person's initials, far right, opening a small menu (Andy: like
     Microsoft 365). A popover, so MOTION_JS places, fades and closes it. */
  details.usermenu{position:relative;border:none;padding:0;margin:0;background:none}
  details.usermenu > summary{list-style:none;cursor:pointer}
  details.usermenu > summary::-webkit-details-marker{display:none}
  header.top .userchip{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;
    border-radius:50%;background:rgba(255,255,255,.16);border:1px solid rgba(255,255,255,.3);color:#fff;
    font-family:var(--sans);font-size:13px;font-weight:600;letter-spacing:.03em;text-decoration:none;
    transition:background .2s ease,box-shadow .2s ease}
  header.top .userchip:hover,details.usermenu[open] > .userchip{background:rgba(255,255,255,.28);
    box-shadow:0 0 0 3px rgba(255,255,255,.14)}
  header.top .userchip:focus-visible{outline:2px solid #fff;outline-offset:2px}
  .usermenu-panel{position:absolute;z-index:70;top:calc(100% + 10px);right:0;width:280px;
    color:var(--ink);text-align:left;padding:6px}
  .umhead{display:flex;align-items:center;gap:12px;padding:12px 12px 14px;border-bottom:1px solid var(--line);
    margin-bottom:6px}
  .umhead b{display:block;font-size:14px;font-weight:600;color:var(--ink)}
  .umhead small{display:block;font-size:12px;color:var(--muted);line-height:1.4;word-break:break-all}
  .umhead .avatar{width:40px;height:40px;font-size:14px}
  .umrole{display:inline-block;margin-top:4px;font-family:var(--mono);font-size:10px;letter-spacing:.06em;
    text-transform:uppercase;color:var(--accent,#1f6f4f);background:var(--accent-soft);border-radius:20px;padding:2px 8px}
  header.top .usermenu-panel a.umitem,.usermenu-panel button.umitem{display:flex;align-items:center;gap:10px;width:100%;
    box-sizing:border-box;padding:9px 12px;border-radius:8px;font-family:var(--sans);font-size:13.5px;font-weight:500;
    color:var(--ink);background:none;border:none;text-align:left;text-decoration:none;cursor:pointer;line-height:1.25}
  header.top .usermenu-panel a.umitem:hover,.usermenu-panel button.umitem:hover{background:var(--accent-soft);text-decoration:none}
  .usermenu-panel .umitem svg{flex:none;color:var(--muted)}
  .usermenu-panel form{margin:0}
  .usermenu-panel .umsep{height:1px;background:var(--line);margin:6px 4px}
  .signin{max-width:430px;margin:48px auto 0}
  .signin .card{padding:30px 30px 26px;box-shadow:0 1px 3px rgba(0,0,0,.05),0 18px 40px rgba(20,24,22,.08);
    animation:tbRise .26s cubic-bezier(.2,.8,.2,1)}
  .signin h2{font-size:20px;margin:0 0 6px}
  .signin .lead{margin-bottom:20px}
  .signin .bigbtn{display:flex;width:100%;justify-content:center;align-items:center;gap:10px;padding:12px 16px;
    font-size:14px;box-sizing:border-box}
  .signin .who{display:flex;flex-direction:column;gap:8px}
  .signin .who button{display:flex;justify-content:space-between;align-items:center;width:100%;text-align:left}
  .signin .who button small{font-weight:400;color:var(--muted);font-size:12px}
  .signin label{display:flex;flex-direction:column;gap:5px;font-size:13px;margin-bottom:12px}
  .signin input[type=text],.signin input[type=email],.usersadd input{padding:9px 11px;border:1px solid var(--line);
    border-radius:8px;background:#fff;font-family:var(--sans);font-size:14px;color:var(--ink)}
  .signin .fine{font-size:12px;color:var(--muted);line-height:1.5;margin:16px 0 0}
  @media (prefers-reduced-motion: reduce){ .signin .card{animation:none} }
  /* Help and What's new. */
  header.top button.ver{cursor:pointer;font-family:var(--mono);font-size:11px;line-height:inherit;text-transform:inherit;letter-spacing:.04em;padding:1px 8px}
  header.top button.ver:hover{background:rgba(255,255,255,.28)}
  /* The help pop-up reads like the Import Builder's, which Andy prefers:
     the same 640px panel, the same .guide sizes, and the guide itself doing
     the scrolling so the heading and the foot stay put. */
  /* A wide pop-up scrolls its BODY, not itself (item 225). It used to do
     the scrolling on the element that carries the rounded corners, so the
     scrollbar sat across the curve at top and bottom right - and the title
     and the close button scrolled away with everything else, which on a
     long What's new meant scrolling back up to shut it. overflow:hidden
     here is what clips the inner scrollbar to the radius. */
  .modal.wide{max-width:640px;max-height:min(86vh,820px);text-align:left;
    display:flex;flex-direction:column;overflow:hidden;padding-bottom:0}
  .modal.wide > .modalscroll{overflow:auto;overscroll-behavior:contain;
    /* Out to the modal's edges, so the scrollbar rides the edge rather than
       floating 26px inside it, then the padding put back within. */
    margin:0 -26px;padding:2px 26px 26px}
  .helpguide{margin-top:6px;padding-right:2px}
  .helpguide details{margin-bottom:8px}
  .helpguide details:last-child{margin-bottom:2px}
  .helpbody{padding:8px 0;font-size:12.5px;line-height:1.6;color:var(--ink-2,#33403a)}
  .helpbody p{margin:0 0 8px;color:var(--ink-2,#33403a);font-size:12.5px}
  .helpbody ul,.helpbody ol{margin:0 0 8px;padding-left:18px;font-size:12.5px;line-height:1.6}
  .helpbody li{margin:0 0 4px}
  .helpbody b,.helpbody strong{color:var(--ink)}
  .wnver{font-family:var(--mono);font-size:11px;color:var(--muted);margin:0 0 12px}
  /* The way to report a bug, at the foot of both help pop-ups. */
  .helpfoot{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
    margin-top:14px;padding-top:14px;border-top:1px solid var(--line);font-size:12.5px;color:var(--muted)}
  .reportbtn{display:inline-flex;align-items:center;gap:7px}
  .reportbtn svg{flex:none;color:var(--accent)}
  button.reportbtn:hover svg{color:inherit}
  /* Reporting a bug or asking for something. */
  #report-form .fld{display:block;margin:14px 0 5px}
  #report-form input[type=text],#report-form textarea{width:100%;box-sizing:border-box;
    padding:9px 11px;border:1px solid var(--line);border-radius:8px;background:#fff;font:inherit;
    font-size:14px;color:var(--ink)}
  #report-form textarea{line-height:1.5;resize:vertical}
  #report-form input[type=text]:focus,#report-form textarea:focus{outline:none;
    border-color:var(--accent-line);box-shadow:0 0 0 3px var(--accent-soft)}
  #report-form input[type=file]{font-size:12.5px;color:var(--muted)}
  .reportkind{margin-bottom:2px}
  .reportkind button{padding:7px 14px;font-size:12px;cursor:pointer}
  .reporthint{margin:10px 0 0;font-size:12px;color:var(--muted)}
  /* Item 294 (Andy, 18 September): *"On the add new popup can we tweak to
     save some space so the box for typing in can be bigger. Make the whole
     popup wider ... This should mean the editor can be at least a few more
     lines taller"*. Only this pop-up: What's new and the two help pop-ups
     share `.modal.wide` and were not what he was looking at. The width and
     the shortened hint together are what get that sentence onto one line;
     either alone very nearly does it. */
  #report-modal .modal.wide{max-width:780px}
  /* Item 312 (Andy): *"open in the middle with the blur behind it and make
     it quite tall"*. Narrower than .modal.wide, because it holds one column
     of rows; taller, because the whole list should be in front of you rather
     than a window onto it. The rows fill the width here, where .sortlist is
     held to 480px on a settings card beside other things. */
  #defmenu-modal .modal.wide{max-width:520px;max-height:min(86vh,860px)}
  /* The form is between the modal and its scrolling part, so the buttons
     can sit outside the scroll and stay put while the list moves. That
     breaks `.modal.wide > .modalscroll`, a direct-child rule, so the
     scrolling and the edge margins are put back here - the same shape
     `.popover-panel.fit > form` already uses for the schedule editor. */
  #defmenu-modal .modal.wide > form{display:flex;flex-direction:column;
    min-height:0;flex:1 1 auto}
  #defmenu-modal .modal.wide > form > .modalscroll{overflow:auto;
    overscroll-behavior:contain;margin:0 -26px;padding:2px 26px 4px;
    flex:1 1 auto;min-height:min(50vh,400px)}
  #defmenu-modal .actionrow{margin-top:0;padding:14px 0 22px}
  #defmenu-modal .sortlist{max-width:none;margin-top:12px}
  #defmenu-modal .reporthint{margin-top:0}
  /* Six lines to ten. The pop-up scrolls inside itself on a short screen,
     which is what .modalscroll is for. */
  #report-modal .rtedit{min-height:220px}
  /* Item 295 replaced this pop-up's own copy of the button row with the
     shared `.actionrow` in APP_CSS, which every other one now wears too.
     BASE_CSS still stacks `.modal-actions` full width, and still should:
     that is right for a dialog offering two real alternatives. */
  .reporterr{margin-top:12px}
  .reportthanks .thanksline{font-size:14.5px;margin:6px 0 0}
  .whatsnew{list-style:none;margin:0;padding:0}
  .whatsnew li{display:grid;grid-template-columns:92px 1fr;gap:2px 14px;padding:11px 0;border-top:1px solid var(--line)}
  .whatsnew .wndate{grid-row:span 2;font-family:var(--mono);font-size:11px;color:var(--muted);padding-top:2px}
  .whatsnew b{font-size:14px;color:var(--ink)}
  .whatsnew span:last-child{font-size:13px;color:var(--ink-2,#33403a);line-height:1.5}
  header.top .jobpill{display:none;position:relative;overflow:hidden;align-items:center;gap:7px;
    font-family:var(--mono);font-size:11px;letter-spacing:.03em;color:#fff;text-decoration:none;
    background:rgba(255,255,255,.13);border:1px solid rgba(255,255,255,.24);
    padding:4px 12px 5px;border-radius:20px;max-width:min(430px,60vw);
    transition:background .2s ease}
  header.top .jobpill:hover{background:rgba(255,255,255,.2)}
  header.top .jobpill.show{display:inline-flex;animation:jobIn .22s cubic-bezier(.2,.8,.2,1)}
  header.top .jobpill.closing{animation:jobOut .16s ease-in forwards}
  header.top .jobpill .jobtext{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
  header.top .jobpill .jobpct{font-weight:600;font-variant-numeric:tabular-nums}
  header.top .jobpill .jobwait{color:#bfe0cf;white-space:nowrap}
  header.top .jobpill .jobdot{flex:none;width:7px;height:7px;border-radius:50%;background:#8ef0b8;
    box-shadow:0 0 0 0 rgba(142,240,184,.6);animation:jobPulse 1.8s ease-out infinite}
  header.top .jobpill .jobbar{position:absolute;left:0;bottom:0;height:2px;width:0;
    background:rgba(255,255,255,.75);transition:width .6s ease}
  header.top .jobpill.warn{background:#f3c969;border-color:#e2b24a;color:#3b2a00}
  header.top .jobpill.warn:hover{background:#f6d27f}
  header.top .jobpill.warn .jobwait{color:inherit;opacity:.7}
  header.top .jobpill.warn .jobdot{background:#b3541e;box-shadow:none;animation:none}
  @keyframes jobIn{from{opacity:0;transform:translateY(-4px) scale(.97)}to{opacity:1;transform:none}}
  @keyframes jobOut{to{opacity:0;transform:translateY(-3px) scale(.98)}}
  @keyframes jobPulse{0%{box-shadow:0 0 0 0 rgba(142,240,184,.55)}70%{box-shadow:0 0 0 6px rgba(142,240,184,0)}100%{box-shadow:0 0 0 0 rgba(142,240,184,0)}}
  @media (prefers-reduced-motion: reduce){
    header.top .jobpill.show,header.top .jobpill.closing,header.top .jobpill .jobdot{animation:none}
    header.top .jobpill .jobbar{transition:none}
  }


  /* Above the green header, and it stays there (item 233). It was allowed
     to scroll away on the grounds that it states a fact rather than
     offering a control - but a warning you can scroll past is missing
     exactly when somebody is deep in a screen and has forgotten which copy
     they are on. The sticky rule and the measurements are in the header's
     own media query above. */
  /* The command that started a run, folded away (item 227). Quiet enough
     that it does not compete with what actually happened, and open to
     anyone who wants to paste it into a bug report. */
  .techdetails{margin-top:10px;border:none;padding:0}
  .techdetails > summary{cursor:pointer;display:inline;font-size:12px;
    color:var(--muted);padding:2px 0}
  .techdetails > summary:hover{color:var(--ink)}
  .techdetails > pre{margin:8px 0 0;white-space:pre-wrap;word-break:break-all;
    font-size:11.5px}
  /* Item 235 - what happened while you were not looking, on the account
     menu. The count sits on the initials; the list is inside the panel. */
  .userchip{position:relative}
  /* Item 236 - Andy: *"The bubble needs moving up and to the right a
     little so it doesnt cover the initials. Can it be a circe too?"*. The
     chip is a circle, so a badge tucked into the corner of its BOX sat
     over the initials rather than on the edge. A round badge, its centre
     roughly where the circle's own edge runs at 45 degrees. */
  .userchip .unread{position:absolute;top:-10px;right:-10px;width:18px;height:18px;
    padding:0;border-radius:50%;background:var(--badge,#c0452f);color:#fff;font-size:10px;
    font-weight:600;line-height:18px;text-align:center;font-family:var(--sans);
    border:2px solid var(--brand,#0a5c36);box-sizing:content-box}
  .umnotes{max-height:40vh;overflow:auto;padding:4px 0}
  .umnote-head{display:flex;justify-content:space-between;align-items:center;
    padding:2px 12px 6px;font-size:11px;color:var(--muted);text-transform:uppercase;
    letter-spacing:.04em;font-family:var(--mono)}
  .umclear{margin:0}
  .umclear .linky{background:none;border:none;padding:0;cursor:pointer;
    font-size:11px;color:var(--accent,#1f6f4f);text-decoration:underline;
    font-family:var(--mono);letter-spacing:.03em}
  .umnote{display:block;padding:7px 12px;font-size:12.5px;line-height:1.45;
    color:var(--ink);text-decoration:none;border-left:3px solid transparent}
  a.umnote:hover{background:var(--panel);text-decoration:none}
  .umnote.new{border-left-color:var(--accent,#1f6f4f);background:var(--accent-soft,#eef5f1)}
  .umn-title{display:block}
  .umn-when{display:block;font-size:10.5px;color:var(--muted);margin-top:2px;
    font-family:var(--mono)}
  .umnote-empty{padding:8px 12px;font-size:12px;color:var(--muted);line-height:1.5}
  .copybar{background:#8c2f24;color:#fdeeec;font-size:13px;line-height:1.4;
    padding:7px 26px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
  .copybar strong{font-weight:650;letter-spacing:.01em}
  .copybar a{color:#fff;text-decoration:underline;text-underline-offset:2px}
  .copybar a:hover{text-decoration:none}
  @media (max-width:600px){.copybar{padding:7px 16px}}
