/* Coordinate desk stylesheet.
   Extracted from index.php 13 Sep 2026 - it was one static <style>
   block with no PHP in it. Loaded from index.php with a filemtime
   cache-buster, same pattern as trace.js. */
  :root{
    /* Default column width: proportional, with sane end-stops.
       The splitter overrides this with an explicit px value. */
    --panel-w: clamp(130px, 38%, 440px);
    --bar: 38px;
    --divider: clamp(3px, 1.1vw, 7px);

    /* ---- THE THEME, 18 Sep 2026 -------------------------------------
       ON :root AND NOT ON .omee, because this is the one place both
       stylesheets can read. The desk is deliberately a SIBLING of the
       Omee shell, not a child of it - that is what stopped Tailwind's
       reset wrecking its type scale - so a variable set on the shell
       would never reach it.

       EVERY DEFAULT IS THE LITERAL IT REPLACED, to the digit. With no
       customer theme set, the page must render pixel-for-pixel as it did
       before this block existed; that was checked by screenshot diff,
       not by eye.

       The page overwrites these at boot from /console/me when an account
       has chosen its own. The -hover / -edge / -soft shades are
       DERIVED from the chosen colour there, so an account picks three
       things (primary, accent, six statuses) rather than eleven.

       WHAT IS NOT HERE: the greys and surfaces - some seventy literals
       further down. They only become variables when a dark palette is
       designed for them, one by one. Turning them into variables with no
       second value would change nothing on screen, and a dark mode that
       has not been designed is worse than none. */
    /* THE TEMPLATE'S PURPLE (19 Sep 2026, was #337ab7). One primary
       colour across the console and the template it sits in - buttons,
       links, the selected job - not a blue beside a purple. The shades
       below are applyTheme()'s own derivation of it, to the digit. */
    --fd-primary:       #7b38f2;   /* links, the Add button, job names   */
    --fd-primary-hover: #602cbd;
    --fd-primary-edge:  #6f32da;
    --fd-primary-soft:  #f4effe;   /* selected job, hovered list row     */
    --fd-primary-wash:  #f8f5fe;   /* hovered drop zone                  */
    --fd-primary-wash2: #f0e9fe;   /* drop zone with a file over it      */
    /* The field that has the cursor. Follows the primary colour, or a
       green theme gets a blue focus ring left over from the default. The
       sign-in form keeps its own literal: it is seen before there is any
       account to take a colour from. */
    --fd-focus:         #a374f6;
    --fd-focus-glow:    #a374f680;
    --fd-accent:        #ff8000;   /* the Refresh button                 */
    --fd-accent-edge:   #e67300;

    /* THE SIX JOB STATES. One palette, and it paints the status word,
       the map pin (PIN_COLOUR reads the same values at boot) and the
       dashboard tile. Colour means status and nothing else. */
    --st-Unassigned: #2e9b3f;
    --st-Assigned:   #2266d3;
    --st-CheckedIn:  #d9342f;
    --st-Arrived:    #0d8f9e;
    --st-Completed:  #6b7480;
    --st-Cancelled:  #9aa1a8;
  }
  *{box-sizing:border-box}
  html,body{height:100%;margin:0}
  body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;color:#000;
       background:#fff;display:flex;flex-direction:column;overflow:hidden;
       -webkit-text-size-adjust:100%}

  /* ---------------- top bar ---------------- */
  .topbar{display:flex;align-items:stretch;background:#f5f5f5;border-bottom:1px solid #ddd;
          height:var(--bar);flex:0 0 auto}
  .brand{padding:10px 14px;color:#888;white-space:nowrap}
  /* This desktop's own clock, sitting before the live dot. Tabular
     figures so the seconds do not jiggle the bar every tick. */
  .deskclock{display:flex;align-items:center;padding:0 4px 0 10px;font-size:12px;
             color:#555;white-space:nowrap;font-variant-numeric:tabular-nums;
             font-feature-settings:"tnum"}
  /* BACKTRACK's announcement. Loud enough to notice while we are testing
     the trigger, and click-to-dismiss so it is never in the way. */
  .btmsg{display:none}
  .btmsg.show{display:flex;align-items:center;padding:0 12px;font-size:12px;
              font-weight:600;color:#7a4a00;background:#fff3d6;
              border-left:1px solid #e8d49a;border-right:1px solid #e8d49a;
              white-space:nowrap;cursor:pointer}
  .btmsg.show:hover{background:#ffe9b8}
  .live{display:flex;align-items:center;gap:6px;padding:0 12px;font-size:12px;color:#777;white-space:nowrap}
  .dot{width:8px;height:8px;border-radius:50%;background:#bbb;flex:0 0 auto}
  .dot.ok{background:#2e9b3f}.dot.busy{background:#e0a800}.dot.err{background:#d9534f}
  /* The sound indicator is the LIVE indicator wearing a speaker: same
     dot, same size, same grey. It reports a state, so it should read
     like the other thing on this bar that reports a state - a button
     box beside it looked like a control that had wandered in from
     somewhere else. It happens to be clickable, which is what the
     cursor and the hover are for. */
  .live.snd{cursor:pointer;padding-left:2px;user-select:none}
  .live.snd:hover{color:#333}
  .live.snd .gl{font-size:14px;line-height:1}
  .live.snd.off .gl{opacity:.5}
  .vpick{margin-left:auto;display:flex;align-items:center;gap:7px;padding:0 14px;
         font-size:12px;color:#888;white-space:nowrap}
  .vpick select{font:inherit;font-size:13px;color:var(--fd-primary);font-weight:600;
                padding:4px 6px;border:1px solid #ccc;border-radius:4px;background:#fff;
                max-width:170px}
  .tabs{display:flex}
  .tab{padding:10px 18px;color:#555;cursor:pointer;border-left:1px solid #e2e2e2;
       user-select:none;background:#f5f5f5;white-space:nowrap}
  .tab:hover{background:#ededed}
  .tab.active{background:#e4e4e4;color:#222}

  /* ---------------- layout ---------------- */
  .main{flex:1 1 auto;display:flex;min-height:0;position:relative}
  .panel{width:var(--panel-w);flex:0 0 var(--panel-w);display:flex;flex-direction:column;
         min-height:0;background:#fff;
         /* Everything inside sizes off the COLUMN's width, not the screen's.
            Drag the splitter narrow on a 27" monitor and the type shrinks
            exactly as it does on a phone - same rule, one code path.
            1cqi = 1% of this column's width. */
         container-type:inline-size;
         --pad:      clamp(3px,   2.9cqi, 14px);
         /* Only the FLOORS were raised (13 Sep 2026). On a phone the panel
            is ~150px wide, so 3.7cqi lands under the minimum and the floor
            is what you actually read - these were 6.5-11px. The cqi terms
            and the ceilings are untouched, so desktop is exactly as it was. */
         --fs-h1:    clamp(13px,  6.6cqi, 26px);
         --fs-name:  clamp(9.5px, 4.0cqi, 14px);
         --fs-body:  clamp(9px,   3.7cqi, 13px);
         --fs-label: clamp(9px,   3.5cqi, 13px);
         --fs-input: clamp(10px,  3.9cqi, 14px);
         --fs-btn:   clamp(9px,   3.6cqi, 13px);
         --fs-small: clamp(8px,   3.2cqi, 12px)}
  .mapwrap{flex:1 1 auto;position:relative;min-width:0;
           /* map chrome sizes off the MAP, as the column does off itself */
           container-type:inline-size;
           --mapui:   clamp(8px, 2.4cqi, 13px);
           --mapbtn:  clamp(21px, 5.2cqi, 30px)}
  #map{position:absolute;inset:0}
  #histmap{position:absolute;inset:0}
  /* History reuses .panel/.splitter/.mapwrap wholesale. The ONLY thing
     that must differ is WHERE the column width lives: the Jobs splitter
     writes --panel-w on the document root, so History scopes its own
     --panel-w onto its pane element. A CSS variable set on #pane-history
     wins for everything inside it, so the two panes resize independently
     and nothing here touches the Jobs page. Every future split page does
     the same: set --panel-w on its own pane, call makeSplitPane on it. */
  #pane-history{--panel-w: clamp(160px, 30%, 460px)}
  .histform{padding:12px 14px;display:grid;grid-template-columns:1fr;gap:6px;overflow-y:auto}
  .histform label{font-size:12px;color:#5A6B7C;font-weight:600;margin-top:6px}
  .histform input,.histform select{padding:6px 8px;font:inherit;
        border:1px solid #B6C2CE;border-radius:4px;width:100%;box-sizing:border-box}
  .histacts{display:flex;gap:8px;margin-top:12px}
  .histmsg{font-size:12px;color:#7a8797;line-height:1.5;margin-top:10px}

  /* draggable splitter */
  .splitter{flex:0 0 var(--divider);background:#e3e3e3;cursor:col-resize;position:relative;
            border-left:1px solid #d5d5d5;border-right:1px solid #d5d5d5;
            touch-action:none;z-index:5}
  .splitter.drag{background:var(--fd-primary)}
  /* :hover only where a real pointer exists - on a touchscreen it sticks
     after a tap and leaves the bar stuck blue */
  @media (hover:hover){ .splitter:hover{background:var(--fd-primary)} }
  .splitter::after{content:"";position:absolute;top:50%;left:50%;width:0;
                   height:clamp(18px,4vh,30px);margin-top:calc(clamp(18px,4vh,30px)/-2);
                   border-left:clamp(1px,.25vw,2px) dotted #999;transform:translateX(-50%)}
  @media (hover:hover){ .splitter:hover::after{border-color:#fff} }
  .splitter.drag::after{border-color:#fff}
  /* On touch the bar stays visually thin but gets an invisible wider grab
     area, so it is easy to drag without being a fat blue stripe. */
  @media (hover:none){
    .splitter::before{content:"";position:absolute;top:0;bottom:0;left:-7px;right:-7px}
  }
  body.resizing{cursor:col-resize;user-select:none}
  body.resizing iframe,body.resizing .gm-style{pointer-events:none}

  /* ---------------- panel ---------------- */
  .panel-head{padding:var(--pad) var(--pad) calc(var(--pad)*.6);flex:0 0 auto}
  .panel-head h1{margin:0 0 calc(var(--pad)*.7);font-size:var(--fs-h1);font-weight:400;line-height:1.1}
  /* Which truck this page is for. Sized off the heading so it stays a
     couple of touches smaller at every column width, not just this one. */
  .h1-veh{font-size:.62em;color:var(--fd-primary);font-weight:600;white-space:nowrap}
  .h1-veh.all{color:#8a9199;font-weight:400}
  /* Per-truck online/offline, beside the truck name. Green once the desk
     has heard the truck's app (sign-in GPS fix + its live poll); red when
     it has signed off. Hidden in the All-Trucks view. */
  /* ---- Setup as a drop-down ---------------------------------------
     Opens on hover on a desk, and the tab itself is still a plain click
     that goes to the whole Setup page - so nothing is reachable ONLY by
     hovering, which would strand a touch screen. */
  .tab-menu{position:relative}
  .tab-menu .caret{font-size:.75em;margin-left:5px;opacity:.65}
  .tabdrop{display:none;position:absolute;top:100%;right:0;z-index:1200;
           min-width:150px;background:#fff;border:1px solid #d5d5d5;
           border-top:none;box-shadow:0 3px 10px rgba(0,0,0,.14)}
  .tab-menu:hover .tabdrop{display:block}
  .tabdrop.shut{display:none !important}
  .tabdrop-i{padding:9px 14px;white-space:nowrap;color:#333;font-weight:400;
             cursor:pointer;text-align:left}
  .tabdrop-i:hover{background:var(--fd-primary-soft);color:var(--fd-primary)}

  /* [FD 19 Sep 2026] THIS BLOCK IS COPIED FROM coordinate-app's desk.css,
     whole. The one here before was an older copy, taken before the word
     LOCATION was replaced by the antenna, and it had no rules for the
     dish or its waves at all. */
  .veh-status{display:none;align-items:center;gap:5px;margin-left:10px;
    font-size:.5em;font-weight:600;vertical-align:middle;white-space:nowrap;
    text-transform:uppercase;letter-spacing:.04em}
  .veh-status.show{display:inline-flex}
  .veh-status .dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
  /* LOCATION pill. Same green dot at two speeds: a gentle breathe while
     fixes arrive on schedule, a fast blink once one is overdue. Grey and
     still when no fix has ever arrived - that is a different thing from
     late and must not look like it. */
  /* ONE animation for every state - index.php sets its duration, from a
     calm 7s breathe on schedule down to 0.6s at thirty minutes of silence.
     Colour is the other axis: green, amber at 15 min, red at 30. Grey and
     still means no fix has ever arrived, which is not the same as late. */
  .veh-status .dot{animation:fixpulse 7s ease-in-out infinite}
  .veh-status.ok{color:#2e9b3f}   .veh-status.ok   .dot{background:#2e9b3f}
  .veh-status.warn{color:#b07d00} .veh-status.warn .dot{background:#e0a800}
  .veh-status.bad{color:#c0392b}  .veh-status.bad  .dot{background:#d9534f}
  .veh-status.none{color:#8a9199}
  .veh-status.none .dot{background:#c4c9ce;animation:none}
  @keyframes fixpulse{0%,100%{opacity:1;transform:scale(1)}
                      50%{opacity:.30;transform:scale(.78)}}
  /* THE DISH, where the word "Location" used to be. Sized in em off the
     pill, so it follows the heading at every column width exactly as the
     text does. Its colours are its own and do not change with the state -
     the dot and the colour of the time carry that. */
  .veh-status .vs-ant{width:1.3em;height:1.3em;flex:0 0 auto;overflow:visible;
    margin-right:1px;vertical-align:middle}
  /* ---- THE WAVES ARE THE DESK CALLING ------------------------------
     Not the truck answering - that is what the dot and the time say.

       moving      the desk is asking this truck for its position, which
                   it does for as long as the driver's shift is open.
       still       nobody is signed on to it, so there is no question in
                   the air and nothing should be leaving the antenna.

     COLOUR SAYS WHETHER ANYBODY IS PICKING UP, on the pill's own
     escalation so all three parts of it agree: green while fixes arrive,
     amber at fifteen minutes of silence, red at thirty. A red wave still
     moving is the desk calling a truck that is not answering - which is
     the state worth walking across the office for. */
  .veh-status .vs-waves{fill:#2e9b3f}
  .veh-status.warn .vs-waves{fill:#e0a800}
  .veh-status.bad  .vs-waves{fill:#d9534f}
  .veh-status.none .vs-waves{fill:#9aa5b1}
  /* Signed off: grey and still, whatever the fix age says. The desk is
     not calling, so the antenna is not sending. */
  .veh-status:not(.sending) .vs-waves{fill:#c4c9ce}

  /* Each wave grows out of the feed horn and fades, the outer a beat
     behind the inner. The origin is the horn, in viewBox units, so the
     wave leaves the antenna rather than the middle of the picture. */
  /* The origin is the FEED HORN, which is outside each arc's own
     outline - the arcs curve around it. fill-box puts the percentages
     on the arc's own box, and these two are where the horn sits in
     each: measured off the artwork, not guessed. */
  .vs-w1,.vs-w2{transform-box:fill-box}
  .vs-w1{transform-origin:31.9% 62.7%}
  .vs-w2{transform-origin:17.1% 67.5%}
  .veh-status.sending .vs-w1{animation:vswave 2.4s ease-out infinite}
  .veh-status.sending .vs-w2{animation:vswave 2.4s ease-out infinite .45s}
  @keyframes vswave{0%{opacity:0;transform:scale(.45)}
                    28%{opacity:1}
                    100%{opacity:0;transform:scale(1.18)}}
  @keyframes fixblink{0%,49%{opacity:1}50%,100%{opacity:.12}}
  /* A dot that never stops moving is punishing for some people, and the
     text still carries the whole answer. */
  @media (prefers-reduced-motion: reduce){
    .veh-status .dot{animation:none !important}
    /* The waves stop, drawn in full rather than mid-fade. */
    .vs-w1,.vs-w2{animation:none !important;opacity:1;transform:none}
  }
  /* Add and Refresh make or reload the list; Locate and Archive act on the
     whole day's work. Different jobs, so they sit at opposite ends. */
  .btnrow{display:flex;gap:calc(var(--pad)*.5);align-items:center;flex-wrap:wrap}
  .btnrow .spacer{margin-left:auto}
  /* ONE HEIGHT FOR THE WHOLE ROW, STATED.

     Every button here was sized by its CONTENTS: text height plus
     padding. That is fine while the contents are words, but "✚", "⌖",
     "⇩" and the old "⟳" are glyphs from whichever fallback font has
     them, and those fonts disagree about how tall a line is - so each
     button ended up a slightly different height and no two of them for
     the same reason. Add looked short next to Locate; the SVG refresh
     then made its own height again.

     The value is exactly what a plain text .btn works out to today, so
     nothing in the row changes size - the odd ones out simply snap to
     it. Both terms are container-query units, so it still scales with
     the panel. */
  .btnrow .btn{height:calc(var(--fs-btn)*1.4 + var(--pad)*.9 + 2px);
               padding-top:0;padding-bottom:0;
               display:inline-flex;align-items:center;justify-content:center}
  /* THE REFRESH ARROW IS A VECTOR, NOT A CHARACTER.

     "⟳" was drawn by whichever fallback font happened to have it, and
     those fonts give the glyph a great deal of built-in padding - so
     raising the point size mostly grew the empty space around the arrow
     while the arrow itself stayed small. Exactly the problem the phone
     had, and the same fix: an SVG fills the box it is given.

     Sized and coloured to match the app's button - the same Material
     refresh path, the same share of the button's height, the same
     VehOrange. Two screens showing one system should not have two
     different refresh buttons.

     The padding stays as it was, so the button keeps the height of the
     +Add beside it. */
  .rfsh{padding:calc(var(--pad)*.3) calc(var(--pad)*.7);line-height:0;
        display:inline-flex;align-items:center;justify-content:center}
  .rfsh svg{width:calc(var(--fs-btn)*1.7);height:calc(var(--fs-btn)*1.7);
            fill:currentColor;display:block}
  .btn{border:1px solid transparent;border-radius:4px;cursor:pointer;line-height:1.4;
       font-family:inherit;font-size:var(--fs-btn,13px);
       padding:calc(var(--pad)*.45) calc(var(--pad)*.85)}
  .btn-blue{background:var(--fd-primary);border-color:var(--fd-primary-edge);color:#fff}.btn-blue:hover{background:var(--fd-primary-hover)}
  .btn-red{background:#d9534f;border-color:#d43f3a;color:#fff}.btn-red:hover{background:#c9302c}
  /* VehOrange - the truck marker's own colour, and the app's refresh.
     Red in this system means a job that is checked in; a permanent red
     block above the list spends that meaning on a button that is not an
     alarm. */
  .btn-orange{background:var(--fd-accent);border-color:var(--fd-accent-edge);color:#fff}
  .btn-orange:hover{background:var(--fd-accent-edge)}
  .btn-green{background:#5cb85c;border-color:#4cae4c;color:#fff}.btn-green:hover{background:#449d44}
  .btn-grey{background:#e6e6e6;border-color:#ccc;color:#333}
  .btn[disabled]{opacity:.55;cursor:default}

  .banner{margin:0 var(--pad) calc(var(--pad)*.5);padding:calc(var(--pad)*.6);
          border-radius:4px;font-size:var(--fs-small);display:none;line-height:1.4}
  .banner.warn{display:block;background:#fcf3cd;border:1px solid #f2e0a0;color:#7a6220}
  .banner.bad{display:block;background:#f8d7da;border:1px solid #f0b6bb;color:#7d2b33}
  .banner .btn{margin-left:8px;vertical-align:middle}

  /* ---------------- form ---------------- */
  .formscroll{overflow-y:auto;flex:0 0 auto;max-height:62%;border-bottom:1px solid #ddd}
  .formscroll.hidden{display:none}
  /* While the form is open it gets the WHOLE column.
     The job list underneath was costing nearly 40% of the height and
     answering nothing - you are typing into a form, not reading the list -
     so every field below Notes sat behind a second scrollbar. */
  body.form-open .formscroll{flex:1 1 auto;max-height:none;border-bottom:none}
  body.form-open .joblist{display:none}
  .form{padding:0 var(--pad) calc(var(--pad)*1.2)}
  .form label{display:block;font-weight:700;font-size:var(--fs-label);
              margin:calc(var(--pad)*.7) 0 calc(var(--pad)*.3)}
  .form label .muted{font-weight:400;color:#999;font-size:var(--fs-small);margin-left:5px}
  .form input,.form select,.form textarea{width:100%;border:1px solid #ccc;border-radius:4px;
       font-family:inherit;background:#fff;color:#333;font-size:var(--fs-input);
       padding:calc(var(--pad)*.5) calc(var(--pad)*.6)}
  .form input:focus,.form select:focus,.form textarea:focus{outline:none;
       border-color:var(--fd-focus);box-shadow:0 0 4px var(--fd-focus-glow)}
  .form textarea{height:clamp(44px,16cqi,64px);resize:vertical;line-height:1.35}
  .form .actions{margin-top:var(--pad);display:flex;gap:calc(var(--pad)*.5);
                  align-items:center;flex-wrap:wrap}
  .form .actions .spacer{margin-left:auto}
  .geo{font-size:var(--fs-small);color:#888;margin-top:calc(var(--pad)*.5);min-height:14px}

  /* ---------------- address autocomplete ---------------- */
  .ac-wrap{position:relative}
  .ac-list{position:absolute;left:0;right:0;top:100%;z-index:1200;background:#fff;
           border:1px solid #ccc;border-top:none;border-radius:0 0 4px 4px;
           box-shadow:0 6px 14px rgba(0,0,0,.15);max-height:260px;overflow-y:auto;display:none}
  .ac-list.open{display:block}
  .ac-item{padding:calc(var(--pad)*.55) calc(var(--pad)*.65);font-size:var(--fs-body);
           cursor:pointer;border-bottom:1px solid #f0f0f0;line-height:1.35}
  .ac-item:last-child{border-bottom:none}
  .ac-item:hover,.ac-item.on{background:var(--fd-primary-soft)}
  .ac-item b{color:#222}
  .ac-item span{color:#888;display:block;font-size:var(--fs-small);margin-top:2px}
  .ac-note{padding:8px 10px;font-size:12px;color:#999}

  /* ---------------- images ---------------- */
  .dropzone{border:2px dashed #ccc;border-radius:6px;text-align:center;color:#777;
            cursor:pointer;background:#fafafa;transition:.15s;line-height:1.35;
            font-size:var(--fs-body);padding:var(--pad) calc(var(--pad)*.6)}
  .dropzone:hover{border-color:var(--fd-primary);background:var(--fd-primary-wash);color:var(--fd-primary)}
  .dropzone.over{border-color:var(--fd-primary);background:var(--fd-primary-wash2);color:var(--fd-primary-hover)}
  .dropzone.full{opacity:.5;cursor:not-allowed;border-style:solid}
  .dropzone .hint{font-size:var(--fs-small);color:#aaa;margin-top:4px}
  .thumbs{display:grid;gap:calc(var(--pad)*.5);margin-top:calc(var(--pad)*.6);
          grid-template-columns:repeat(auto-fill,minmax(clamp(48px,22cqi,84px),1fr))}
  .thumb{position:relative;padding-top:75%;border:1px solid #ddd;border-radius:4px;
         overflow:hidden;background:#f4f4f4}
  .thumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
  .thumb .rm{position:absolute;top:3px;right:3px;width:20px;height:20px;border:none;
             border-radius:50%;background:rgba(0,0,0,.62);color:#fff;font-size:13px;
             line-height:20px;cursor:pointer;padding:0}
  .thumb .rm:hover{background:#d9534f}
  .thumb .up{position:absolute;left:0;right:0;bottom:0;height:3px;background:#5cb85c;
             width:0;transition:width .2s}
  .thumb.pending{border-color:#e0a800}

  /* ---------------- job list ---------------- */
  .joblist{flex:1 1 auto;overflow-y:auto;min-height:0;-webkit-overflow-scrolling:touch}
  .job{border-bottom:1px solid #e8e8e8;cursor:pointer;
        padding:calc(var(--pad)*.7) var(--pad);font-size:var(--fs-body);line-height:1.35}
  .job:hover{background:#fafafa}
  .job.sel{background:var(--fd-primary-soft)}
  .job-top{display:flex;align-items:baseline;gap:calc(var(--pad)*.4)}
  .job-name{color:var(--fd-primary);font-size:var(--fs-name)}
  .job.sel .job-name{outline:1px dotted #666;outline-offset:2px}
  /* ------------------------------------------------------------------
     STATUS COLOUR - defined ONCE, used everywhere a status is written.

     The map markers already teach the dispatcher a colour language:
     CheckedIn red, Assigned blue, Unassigned green. Any status text in a
     different colour scheme would be a second language for the same
     facts. So the pin colours ARE the text colours, and Completed and
     Cancelled - which have no pin, being off the map - are grey, which
     is what "no longer needs your attention" should look like.
     ------------------------------------------------------------------ */
  .st{font-weight:600}
  .st-Assigned  {color:var(--st-Assigned)}
  .st-CheckedIn {color:var(--st-CheckedIn)}
  .st-Unassigned{color:var(--st-Unassigned)}
  .st-Completed {color:var(--st-Completed)}
  .st-Cancelled {color:var(--st-Cancelled);text-decoration:line-through}

  /* The chain wraps rather than squeezing the job name: in a narrow
     column a long history is two short lines, not one unreadable one. */
  .job-status{margin-left:auto;font-size:var(--fs-body);white-space:nowrap;
              text-align:right}
  .job-chain{margin-left:auto;font-size:var(--fs-small);text-align:right;
             line-height:1.5;max-width:62%;min-width:0}
  .job-chain .sep{color:#8b939b;font-weight:400}
  .job-addr{margin-top:calc(var(--pad)*.35);font-size:var(--fs-body)}
  .job-contact{margin-top:calc(var(--pad)*.35);background:#f0f0f0;font-size:var(--fs-body);
               padding:calc(var(--pad)*.3) calc(var(--pad)*.5)}
  .job-contact i{color:#000}
  .job-notes{margin-top:calc(var(--pad)*.35);font-size:var(--fs-body)}
  .job-notes .nh{display:block;color:#000;font-size:var(--fs-small);
                 margin-bottom:calc(var(--pad)*.15)}
  .job-imgs{margin-top:calc(var(--pad)*.35);font-size:var(--fs-small);color:#000}
  .job-veh{margin-top:calc(var(--pad)*.3);font-size:var(--fs-small);color:#000}
  /* An Assigned job with no truck is a data fault, not a normal state.
     Now that the driver sees only his own list, it is work nobody can
     see - so it is called out in red rather than left looking ordinary. */
  .job-veh.unset{color:#c0392b;font-weight:600}
  .job.flash{animation:fl 2s ease-out}
  @keyframes fl{from{background:#fff6d5}to{background:transparent}}
  .empty{padding:calc(var(--pad)*1.6) var(--pad);color:#999;text-align:center;font-size:var(--fs-body)}

  /* ---------------- map chrome ---------------- */
  /* offset clear of Leaflet's zoom control, which is also top-left */
  .maptoggle{position:absolute;top:8px;left:calc(var(--mapbtn) + 20px);z-index:500;display:flex;
             box-shadow:0 1px 4px rgba(0,0,0,.3);border-radius:2px;overflow:hidden}
  .maptoggle button{border:none;background:#fff;cursor:pointer;font-family:inherit;
                    border-right:1px solid #e0e0e0;font-size:var(--mapui);
                    padding:calc(var(--mapui)*.62) calc(var(--mapui)*1.25)}
  .maptoggle button:last-child{border-right:none}
  .maptoggle button.on{background:#fff;font-weight:700}
  .maptoggle button:not(.on){background:#f5f5f5;color:#666}
  .fs{position:absolute;top:8px;right:8px;z-index:500;background:#fff;border:none;
      cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.3);border-radius:2px;
      width:var(--mapbtn);height:var(--mapbtn);font-size:calc(var(--mapui)*1.15);padding:0}
  /* Fit sits beside fullscreen, one button-width to its left. */
  #fitBtn{right:calc(8px + var(--mapbtn) + 6px)}
  /* A vehicle is a point, not a destination - round badge, centre-anchored,
     so it never reads as another job pin. */
  /* The shadow is a filter on the whole pin, not a box-shadow on a square
     div: it follows the drawn outline, so the tip casts a point too. */
  .pinwrap{filter:drop-shadow(0 1px 1.5px rgba(0,0,0,.45))}
  .pinwrap svg{display:block;overflow:visible}
  .vehwrap{filter:drop-shadow(0 1px 2px rgba(0,0,0,.4))}
  /* No fix for a while - faded, never recoloured. */
  .vehwrap.stale{opacity:.45}
  .vehwrap svg{display:block;overflow:visible}
  .veh-age{color:#888}
  /* Google's info window content is ours; size it off the map pane so it
     scales with the map the way everything else does. */
  .gm-pop{font-size:var(--mapui);line-height:1.45;color:#333;max-width:240px}
  .gm-pop b{color:var(--fd-primary)}
  .gm-pop .btn{font-size:var(--mapui)}
  /* Google's own zoom buttons, brought into the same size scale. */
  .gm-style .gm-bundled-control button{transform-origin:center}

  /* ---------------- other panes ---------------- */
  .pane{display:none;flex:1 1 auto;padding:32px;color:#777;overflow:auto}
  .pane h2{font-weight:400;color:#444;margin-top:0}
  .pane code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:12px}
  .pane-lead{max-width:620px;line-height:1.5}

  /* ---------------- setup tab ---------------- */
  .vform{display:grid;grid-template-columns:110px 1fr;gap:8px 12px;align-items:center;
         max-width:520px;margin:18px 0 8px;background:#fafafa;border:1px solid #e4e4e4;
         border-radius:6px;padding:14px}
  .vform label{color:#666;font-size:13px}
  .vform input,.vform select{padding:7px 8px;border:1px solid #ccc;border-radius:4px;
                             font:inherit;width:100%;box-sizing:border-box}
  .vform-acts{grid-column:1/-1;display:flex;gap:8px;margin-top:4px}
  .vform-acts .spacer{margin-left:auto}
  .vmsg{grid-column:1/-1;font-size:13px;min-height:1em}
  .vmsg.bad{color:#c0392b}.vmsg.ok{color:#2e7d32}
  .vtable{border-collapse:collapse;max-width:760px;width:100%;font-size:13px;margin-top:14px}
  .vtable th{text-align:left;color:#888;font-weight:400;border-bottom:1px solid #ddd;
             padding:6px 10px}
  .vtable td{border-bottom:1px solid #eee;padding:7px 10px;color:#444}
  .vtable tr.retired td{color:#aaa}
  /* Not scoped to .vtable any more - the pairing box uses one too, and a
     "cancel" that renders as plain grey text does not read as clickable. */
  .rowbtn{color:var(--fd-primary);cursor:pointer}
  .rowbtn:hover{text-decoration:underline}
  .rowbtn.danger{color:#c0392b}

  /* ---- phones ---- */
  .sechead{margin:30px 0 0;font-size:17px;font-weight:600}
  .setgrp{margin:14px 0 6px}
  .setgrp-h{font-size:13px;font-weight:600;color:#5A6B7C;text-transform:uppercase;
            letter-spacing:.4px;margin:0 0 6px}
  .setrow{display:grid;grid-template-columns:1fr 190px 210px;gap:12px;align-items:center;
          padding:8px 0;border-top:1px solid #eef1f4}
  .setrow-lab{font-size:14px}
  .setrow-help{font-size:12px;color:#7a8797;line-height:1.4;margin-top:2px}
  .setrow-c{display:flex;align-items:center;gap:8px}
  .setrow-c input[type=number],.setrow-c select,.setrow-c input[type=text]{
          width:120px;padding:5px 7px;font:inherit;border:1px solid #B6C2CE;border-radius:4px}
  .setrow-rng{font-size:12px;color:#9aa5b1}
  .setrow-r{display:flex;align-items:center;justify-content:flex-end;gap:8px}
  .setrow-def{font-size:12px;color:#9aa5b1;white-space:nowrap}
  .setrow-reset{padding:3px 8px;font-size:12px}
  @media(max-width:720px){.setrow{grid-template-columns:1fr;gap:4px}
          .setrow-r{justify-content:flex-start}}
  .dev-live{color:#2e9b3f;font-weight:600}
  .dev-old {color:#c47f00;font-weight:600}
  .dev-gone{color:#999}
  .vtable tr.revoked td{color:#bbb;text-decoration:line-through}
  .vtable tr.revoked td:last-child{text-decoration:none}

  /* The code itself. Big, spaced, and monospaced because it gets read
     out loud - "was that a B or a D" is the whole problem. */
  .paircode{display:inline-block;font:600 30px/1.1 ui-monospace,Menlo,Consolas,monospace;
            letter-spacing:5px;background:#eef6ff;border:1px solid #b9d6f5;
            border-radius:6px;padding:10px 16px;color:#144a86;margin:2px 0}
  .pairbox{margin-top:12px;padding:12px 14px;border:1px solid #b9d6f5;border-radius:6px;
           background:#f7fbff;max-width:760px}
  .pairbox .why{color:#555;font-size:13px;line-height:1.5;margin:6px 0 0}
  .pairbox .cd{color:#888;font-size:13px;margin-left:10px}

  /* ---------------- narrow screens ----------------
     Only page chrome is viewport-driven now. Everything inside the job
     column scales from the column itself (see .panel above), so it looks
     the same whether the column is narrow because the phone is small or
     because the splitter was dragged in. */

  @media (max-width: 900px){
    .brand{display:none}
    .deskclock{padding:0 4px}
    .tab{padding:10px 12px;font-size:13px}
    .live{padding:0 8px}
    .vpick{padding:0 8px}
  }

  @media (max-width: 560px){
    #livetxt{display:none}
    .live{padding:0 6px}
    .tab{padding:10px 9px;font-size:12px}
    /* The dropdown says which truck by itself; the word "Showing" is the
       part that can go when the bar runs out of room. */
    .vpick span{display:none}
    .vpick{padding:0 5px}
    .vpick select{font-size:12px;max-width:120px}
  }

  /* ===================================================================
     FENGEDISPATCH ADDITIONS

     Everything above is coordinate-app's desk.css, taken verbatim so the
     two systems read identically and a fix in one can be carried to the
     other without translating it. Only what this console needs BEYOND
     that is below.
     =================================================================== */

  /* ARRIVED — the one status coordinate-app does not have. It is derived
     by gps_module when the vehicle goes stationary at the job, so it sits
     between CheckedIn and Completed and is coloured to say "there, not
     finished": the same family as CheckedIn's urgency, cooled off. */
  .st-Arrived{color:var(--st-Arrived)}

  /* Who is signed in. The desk had no accounts; this one does. */
  .who{display:flex;align-items:center;gap:8px;padding:0 12px;font-size:12px;
       color:#777;white-space:nowrap}
  .who .btn{font-size:12px;padding:3px 9px}

  /* Sign in / create account. A whole-screen card, because until it is
     answered there is nothing behind it. */
  .door{max-width:400px;margin:8vh auto;padding:22px 24px;border:1px solid #e0e0e0;
        border-radius:8px;background:#fff;box-shadow:0 2px 10px rgba(0,0,0,.06)}
  .door h1{margin:0 0 4px;font-size:22px;font-weight:600}
  .door .sub{color:#888;font-size:13px;margin:0 0 18px}
  .door label{display:block;font-weight:700;font-size:12px;color:#555;margin:10px 0 4px}
  .door input{width:100%;border:1px solid #ccc;border-radius:4px;font:inherit;
              padding:8px 9px}
  .door input:focus{outline:none;border-color:#66afe9;
                    box-shadow:0 0 4px rgba(102,175,233,.5)}
  .door .btn{width:100%;margin-top:16px;padding:9px}
  .door .hint{font-size:12px;color:#999;margin-top:5px}
  .doortabs{display:flex;margin-bottom:16px}
  .doortabs .t{flex:1;text-align:center;padding:8px;cursor:pointer;color:#777;
               background:#f5f5f5;border:1px solid #ddd;user-select:none}
  .doortabs .t:first-child{border-radius:4px 0 0 4px;border-right:none}
  .doortabs .t:last-child{border-radius:0 4px 4px 0}
  .doortabs .t.on{background:#fff;color:#222;font-weight:600}
  .slugbox{display:flex}
  .slugbox input{border-radius:4px 0 0 4px}
  .slugbox .sfx{padding:8px 9px;border:1px solid #ccc;border-left:none;
                border-radius:0 4px 4px 0;background:#f5f5f5;color:#888;
                font:13px ui-monospace,Menlo,Consolas,monospace;white-space:nowrap}

  /* The setup-link box borrows the pairing box's look. */
  .linkbox{margin-top:12px;padding:12px 14px;border:1px solid #b9d6f5;border-radius:6px;
           background:#f7fbff;max-width:760px;word-break:break-all}
  .linkbox .lk{font:13px ui-monospace,Menlo,Consolas,monospace;color:#144a86;
               margin:6px 0}

  /* The map cannot always draw — no key, a rejected key, a blocked
     script. It says which, in the map's own place, rather than leaving a
     grey rectangle nobody can explain. */
  #mapNote{position:absolute;inset:0;display:flex;align-items:center;
           justify-content:center;padding:30px;text-align:center;color:#777;
           background:#fafafa;line-height:1.6}
  #map[hidden],#mapNote[hidden]{display:none}

  /* An empty state that is a fact, not an error. */
  .none{color:#c47f00}

  /* =================================================================
     THE MARKER PICKER

     Swatches sized in the SAME container-query units as everything else
     in this console, so on a narrow window they shrink with the rest
     rather than staying a fixed slab of chrome.

     THE SWATCH IS THE MARKER, drawn from the same SVG the map draws, at
     a size you can actually judge. A named list would make the customer
     click each one to find out what it looks like.
     ================================================================= */
  .mksec{margin:10px 0 18px}
  .mklab{font-weight:600;margin-bottom:6px}
  .mkrow{display:flex;flex-wrap:wrap;gap:10px}

  .mk{width:74px;padding:8px 4px 6px;border:1px solid #ddd;border-radius:6px;
      background:#fff;cursor:pointer;text-align:center;
      display:flex;flex-direction:column;align-items:center;gap:5px;
      transition:border-color .12s,background .12s}
  .mk:hover{border-color:var(--fd-primary);background:var(--fd-primary-wash)}

  /* A fixed-height stage so a 44-tall pin and a 24-tall dot sit on the
     same baseline — otherwise the row jitters as the eye moves along it. */
  .mk .sw{height:44px;display:flex;align-items:flex-end;justify-content:center}
  .mk .sw svg{display:block}
  .mk .nm{font-size:11px;color:#666;line-height:1.2}

  /* CHOSEN IS A STATE, NOT A HOVER. Blue border plus a tick, because on a
     touch screen there is no hover to fall back on and the customer has
     to be able to see which one is live without touching anything. */
  .mk.on{border-color:#2266d3;background:#eef5ff;box-shadow:inset 0 0 0 1px #2266d3}
  .mk.on .nm{color:#2266d3;font-weight:600}
  .mk.busy{opacity:.5;pointer-events:none}
