
  /* project switch — busy state. In cloud mode a switch now re-pulls all 18
     relational islands before the registers reload (ProcureData.repullAll), which
     is plausibly seconds. The content area is NOT blanked: a blank panel reads as
     a crash, and nothing is written until the finish step. Dim + inert instead,
     with a named status line above it. */
  /* padding mirrors .app-main .wrap so the line sits on the content's left edge
     at both widths (the wrap is centred at --wrap-w) */
  .switch-status{display:flex;align-items:center;gap:var(--sp-2);width:100%;max-width:var(--wrap-w,1640px);margin:var(--sp-4) auto var(--sp-0);padding:var(--sp-0) var(--sp-7);color:var(--muted);font-size:13px;font-weight:600}
  .switch-status[hidden]{display:none}
  .sw-spin{width:12px;height:12px;flex:none;border-radius:50%;border:2px solid var(--border2);border-top-color:var(--txt);animation:swspin .7s linear infinite}
  @keyframes swspin{to{transform:rotate(360deg)}}
  @media(prefers-reduced-motion:reduce){ .sw-spin{animation:none} }
  #appWrap[aria-busy="true"]{opacity:.55;pointer-events:none}
  @media(max-width:900px){ .switch-status{padding-left:var(--sp-4);padding-right:var(--sp-4)} }

  /* persistent job switcher (topbar) */
  .job-switcher{display:inline-flex;align-items:center;gap:var(--sp-2);margin-left:var(--sp-3);padding:var(--sp-1) var(--sp-2) var(--sp-1) var(--sp-2);background:var(--panel);border:1px solid var(--border);border-radius:var(--radius-sm,9px);box-shadow:var(--shadow-sm)}
  .job-switcher .js-ic{width:7px;height:7px;border-radius:50%;background:var(--amber);flex:none}
  .job-switcher select{border:none;background:transparent;color:var(--txt);font:600 13px/1 inherit;max-width:200px;cursor:pointer;outline:none;-webkit-appearance:none;appearance:none;padding-right:var(--sp-3)}
  .job-switcher select:focus-visible{box-shadow:var(--ring-focus);border-radius:6px}
  @media(max-width:720px){ .job-switcher{margin-left:var(--sp-2)} .job-switcher select{max-width:120px} }

  /* jobs portfolio */
  .jobs-head{display:flex;align-items:flex-end;justify-content:space-between;margin:var(--sp-1) var(--sp-0) var(--sp-4)}
  .jobs-title{font:700 22px/1.1 var(--mono);letter-spacing:-.01em;margin:0;color:var(--txt)}
  .jobs-sub{margin:var(--sp-2) 0 0;color:var(--muted);font-size:13px}
  .jobs-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--gap-grid)}
  .job-card{display:flex;flex-direction:column;gap:var(--sp-2);text-align:left;padding:var(--pad-card) var(--pad-card) var(--pad-card);background:var(--panel);border:1px solid var(--border);border-radius:var(--radius,14px);box-shadow:var(--shadow-sm);cursor:pointer;transition:transform .12s ease,box-shadow .12s ease,border-color .12s ease;color:var(--txt);font:inherit}
  .job-card:hover{transform:translateY(-2px);box-shadow:var(--shadow-md);border-color:var(--border2)}
  .job-card:active{transform:translateY(0)}
  .job-card.active{border-color:var(--amber);box-shadow:0 0 0 1px var(--amber) inset, var(--shadow-sm)}
  .job-card .jc-top{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-2)}
  .job-card .jc-name{font:700 16px/1.2 inherit;letter-spacing:-.01em}
  .job-card .jc-badge{font:600 10px/1 var(--mono);text-transform:uppercase;letter-spacing:.06em;color:var(--on-accent,#f4f5f6);background:var(--amber);padding:var(--sp-1) var(--sp-2);border-radius:var(--radius-sm)}
  .job-card .jc-addr{color:var(--muted);font-size:12.5px;margin-top:-2px}
  .job-card .jc-kpis{display:flex;flex-wrap:wrap;gap:var(--sp-3) var(--sp-4);margin-top:var(--sp-2);padding-top:var(--sp-3);border-top:1px solid var(--border)}
  .job-card .jc-k{display:flex;flex-direction:column;gap:var(--sp-0)}
  .job-card .jc-kv{font:700 15px/1 var(--mono);color:var(--txt)}
  .job-card .jc-kl{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted2)}
  .job-card-new{align-items:center;justify-content:center;text-align:center;border-style:dashed;background:var(--panel2);color:var(--muted)}
  .job-card-new:hover{border-color:var(--amber);color:var(--txt)}
  .job-card-new .jc-plus{color:var(--amber)}
  .job-card-new .jc-name{font-weight:600}

  /* R-3 leadership portfolio (docs/ROLE-DASHBOARDS-CONTRACT.md §3) — PROPOSED metric row.
     Monochrome data layer: tier is carried by a glyph AND a WORD (▲ late/short ·
     △ watch · ✓ clear), never by hue. The glyph is aria-hidden because the label
     beside it already states the status in words.
     Tier is stepped with the TEXT tokens (--muted -> --txt), not the --viz-*
     ramp: --viz-mid measures 3.3:1 on the card, which is fine for a bar or a dot
     but fails AA for 11–12px type, and here the words ARE the accessible
     mechanism, so they have to be readable. Measured on this build: base 7.03:1,
     high 14.83:1, separation between the two tiers 2.11:1 (the repo's own
     check-viz-contrast threshold is 1.35:1). */
  .jobs-decisions{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--sp-2) var(--sp-4);margin:var(--sp-0) var(--sp-0) var(--sp-4);padding:var(--sp-3) var(--pad-card);background:var(--panel2);border:1px solid var(--border);border-radius:var(--radius-sm,9px)}
  .jobs-decisions .jd-h{font:600 10.5px/1.4 var(--mono);text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
  .jobs-decisions .jd-i{display:inline-flex;align-items:baseline;gap:var(--sp-1);font-size:12.5px;color:var(--muted);line-height:1.5}
  .jobs-decisions .jd-n{font:700 15px/1 var(--mono);color:var(--txt);font-variant-numeric:tabular-nums}
  .jobs-decisions .jd-g{font-size:11px;color:var(--muted);flex:none}
  .jobs-decisions .jd-q{color:var(--muted)}
  .job-card .jc-metrics{display:flex;flex-wrap:wrap;gap:var(--sp-2) var(--sp-3);margin-top:var(--sp-2);padding-top:var(--sp-2);border-top:1px dashed var(--border)}
  /* jobMetricCells() returns "" whenever there is nothing to say — which is the
     WHOLE local-first path, where there are no relational sources to roll up —
     and the element still has to exist for jobsPortfolioPaint() to patch by
     [data-jobm]. So hide the empty row rather than removing it: a dashed rule
     under a card with no metrics beneath it reads as a section that failed to
     load. */
  .job-card .jc-metrics:empty{display:none}
  .job-card .jc-m{display:inline-flex;align-items:baseline;gap:var(--sp-1);font-size:11.5px;line-height:1.4}
  .job-card .jc-mg{font-size:10px;color:var(--muted);flex:none}
  .job-card .jc-mv{font:600 12.5px/1.2 var(--mono);color:var(--muted);font-variant-numeric:tabular-nums}
  .job-card .jc-ml{color:var(--muted)}
  .job-card .jc-m.t-hi .jc-mg,.job-card .jc-m.t-hi .jc-mv,.job-card .jc-m.t-hi .jc-ml{color:var(--txt)}
  .job-card .jc-note{flex:1 0 100%;font-size:11px;color:var(--muted);line-height:1.4}

  /* admin — job access matrix */
  .ax-note{display:flex;gap:var(--sp-2);align-items:flex-start;background:var(--panel2);border:1px solid var(--border);border-radius:var(--radius-sm,9px);padding:var(--sp-3) var(--sp-3);color:var(--muted);font-size:12.5px;line-height:1.5;margin:var(--sp-0) var(--sp-0) var(--sp-4)}
  .ax-note b{color:var(--txt);font-weight:600}
  .rbac-pills{display:inline-flex;flex-wrap:nowrap;max-width:100%;overflow-x:auto;margin:0 var(--sp-0) var(--sp-4);border:1px solid var(--border2);border-radius:var(--r);background:var(--panel)}
  .rbac-pill{padding:var(--sp-2) var(--sp-3);border:0;border-radius:0;background:transparent;color:var(--muted);font:inherit;font-size:12.5px;cursor:pointer;white-space:nowrap;transition:background .15s,color .15s}
  .rbac-pill+.rbac-pill{border-left:1px solid var(--border2)}
  .rbac-pill:hover{color:var(--txt);background:color-mix(in srgb,var(--amber) 8%,transparent)}
  .rbac-pill.on{background:var(--amber);color:var(--on-accent);font-weight:600}
  .rbac-sys{font-size:10px;opacity:.7;margin-left:var(--sp-1);text-transform:uppercase;letter-spacing:.04em}
  .ax-rolesel{padding:var(--sp-1) var(--sp-2);border:1px solid var(--border);background:var(--panel2);color:var(--txt);border-radius:7px;font:inherit;font-size:12.5px}
  /* bottom-right, lifted clear of the transient .saved-pill (bottom-right) and the
     sidebar footer user block (bottom-left) it used to overlap at short viewports */
  .demo-badge{position:fixed;right:var(--sp-4);bottom:var(--sp-8);z-index:9500;background:var(--amber);color:var(--on-accent);font:600 11px/1.4 var(--mono);text-transform:uppercase;letter-spacing:.09em;padding:var(--sp-1) var(--sp-3);border-radius:var(--radius-sm);box-shadow:0 4px 14px rgba(0,0,0,.32);pointer-events:none;opacity:.92}
  .ax-addbar{display:flex;flex-wrap:wrap;gap:var(--sp-2);align-items:center;margin:0 var(--sp-0) var(--sp-4)}
  .ax-addbar input[type=email],.ax-addbar input[type=text]{padding:var(--sp-2) var(--sp-3);border:1px solid var(--border);border-radius:var(--radius-sm,9px);background:var(--panel);color:var(--txt);font:inherit;font-size:13px;min-width:200px}
  .ax-addbar input:focus-visible{outline:none;box-shadow:var(--ring-focus);border-color:var(--amber)}
  .ax-admlab{display:inline-flex;align-items:center;gap:var(--sp-2);color:var(--muted);font-size:13px}
  .ax-tablewrap{overflow:auto;border:1px solid var(--border);border-radius:var(--radius,14px);background:var(--panel);box-shadow:var(--shadow-sm)}
  /* The sticky header below only works if something actually scrolls. With no
     height cap the wrap grows to fit its content and `top:0` has nothing to
     stick inside, so the permission matrix scrolled its action labels off the
     top of the page and you lost track of which column you were clicking.
     Opt-in rather than global: the short tables should keep sizing to content. */
  .ax-tablewrap.tall{max-height:62vh}
  .ax-filter{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-2);flex-wrap:wrap}
  .ax-filter input{flex:1 1 220px;min-width:0}
  .ax-filter .ax-count{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}
  .ax-nomatch{padding:var(--sp-4);color:var(--muted);font-size:13px}
  /* first-run checklist (WF-02): opens itself while a step is outstanding and
     collapses to one line once nothing is left to prompt about */
  .fr-box{border:1px solid var(--border);border-radius:var(--radius,14px);background:var(--panel2);padding:var(--sp-3) var(--sp-4);margin-bottom:var(--sp-4)}
  .fr-sum{cursor:pointer;list-style:none;display:flex;align-items:baseline;gap:var(--sp-3);flex-wrap:wrap}
  .fr-sum::-webkit-details-marker{display:none}
  .fr-sum b{font-size:13px}
  .fr-sum>span{font-size:12px;color:var(--muted)}
  .fr-sum:focus-visible{outline:2px solid var(--txt);outline-offset:3px;border-radius:6px}
  .fr-list{list-style:none;margin:var(--sp-3) 0 0;padding:0;display:flex;flex-direction:column;gap:var(--sp-2)}
  .fr-list li{display:flex;gap:var(--sp-3);align-items:flex-start}
  .fr-mark{flex:0 0 auto;width:16px;text-align:center;font-weight:700;color:var(--muted)}
  .fr-done .fr-mark{color:var(--txt)}
  .fr-todo .fr-mark{color:var(--chase)}
  .fr-b{display:flex;flex-direction:column;gap:2px;min-width:0}
  .fr-b b{font-size:12.5px;font-weight:650;color:var(--txt)}
  .fr-b span{font-size:12px;color:var(--muted);line-height:1.5}
  .fr-done .fr-b b{font-weight:600;color:var(--muted)}
  /* Preview-as-role banner. Body-level and fixed, so no container re-render can
     take away the one control that ends the preview. Bottom rather than top:
     the topbar is a joined segmented bar and a strip above it would break that
     line every time the lens is on. */
  #permPreviewBar{position:fixed;left:50%;transform:translateX(-50%);bottom:calc(var(--sp-5) + env(safe-area-inset-bottom,0px));
    z-index:80;display:flex;align-items:center;gap:var(--sp-4);max-width:min(720px,calc(100vw - var(--sp-6)));
    padding:var(--sp-3) var(--sp-4);border:1px solid var(--border2);border-radius:var(--radius,14px);
    background:var(--panel);color:var(--text);box-shadow:var(--shadow-lg,var(--shadow-sm));font-size:13px;line-height:1.4}
  #permPreviewBar b{font-weight:650}
  #permPreviewBar button{flex:0 0 auto}
  @media (max-width:640px){ #permPreviewBar{align-items:flex-start;flex-direction:column;gap:var(--sp-2)} }
  .ax-table{border-collapse:collapse;width:100%;font-size:13px}
  .ax-table th,.ax-table td{padding:var(--pad-cell-y) var(--pad-cell-x);border-bottom:1px solid var(--border);text-align:left;vertical-align:middle}
  .ax-table thead th{font:600 11px/1 var(--mono);text-transform:uppercase;letter-spacing:.05em;color:var(--muted);background:var(--panel2);position:sticky;top:0}
  .ax-table th.ax-job{text-align:center;white-space:nowrap}
  .ax-table tbody tr:last-child td{border-bottom:none}
  .ax-user{min-width:200px}
  .ax-user b{display:block;color:var(--txt);font-weight:600}
  .ax-user span{display:block;color:var(--muted2);font-size:11.5px}
  .ax-user .ax-adm{display:inline-block;margin-top:var(--sp-0);color:var(--amber);font:600 10px/1 var(--mono);text-transform:uppercase;letter-spacing:.05em}
  .ax-cell{text-align:center}
  .ax-chk{display:inline-flex;cursor:pointer}
  .ax-chk input{width:17px;height:17px;accent-color:var(--amber);cursor:pointer}
  .ax-chk input:disabled{cursor:not-allowed;opacity:.7}
  .ax-subhead{display:flex;align-items:baseline;gap:var(--sp-2);margin:var(--sp-5) var(--sp-0) var(--sp-3)}
  .ax-subhead h3{margin:0;font:600 14px/1.2 var(--sans,inherit);color:var(--txt)}
  .ax-subhead span{color:var(--muted2);font-size:12px}
  /* tri-state section permission control: Full → Read → None (cycles on click) */
  .ax-perm{display:inline-flex;align-items:center;gap:var(--sp-1);min-width:96px;padding:var(--sp-1) var(--sp-2);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--panel2);color:var(--txt);font:600 11px/1 var(--mono);text-transform:uppercase;letter-spacing:.04em;cursor:pointer;transition:border-color .12s,background .12s,color .12s}
  .ax-perm:hover{border-color:var(--amber)}
  .ax-perm:focus-visible{outline:none;box-shadow:var(--ring-focus)}
  .ax-perm .ax-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto;background:currentColor}
  .ax-perm.full{color:#a3a2a0;border-color:rgba(163,162,160,.4)}
  .ax-perm.read{color:#a4a3a1;border-color:rgba(164,163,161,.4)}
  .ax-perm.none{color:var(--muted2);background:transparent}
  .ax-perm:disabled{cursor:not-allowed;opacity:.6}
  .ax-legend{display:flex;flex-wrap:wrap;gap:var(--sp-3);margin:var(--sp-2) var(--sp-0) 0;color:var(--muted2);font-size:11.5px}
  .ax-legend span{display:inline-flex;align-items:center;gap:var(--sp-1)}
  .ax-legend i{width:8px;height:8px;border-radius:50%;display:inline-block}
  /* read-only section lock — neutralises edit controls inside the locked view, but keeps
     subtabs and search usable so the section is still browsable. Local-first / cosmetic. */
  .ro-shield{display:flex;align-items:center;gap:var(--sp-2);margin:var(--sp-0) var(--sp-0) var(--sp-4);padding:var(--sp-2) var(--sp-4);border:1px solid var(--border);border-radius:var(--radius-sm,9px);background:var(--panel2);color:var(--muted);font-size:13px;line-height:1.45}
  .ro-shield svg{flex:0 0 auto;opacity:.8}
  .ro-shield b{color:var(--txt);font-weight:600}
  .ro-lock button:not(.subtab):not([data-roallow]),
  .ro-lock input:not([type=search]):not([data-roallow]),
  .ro-lock textarea:not([data-roallow]),
  .ro-lock select:not([data-roallow]),
  .ro-lock [contenteditable="true"],
  .ro-lock [contenteditable=""]{pointer-events:none !important}
  .ro-lock input:not([type=search]):not([data-roallow]),
  .ro-lock textarea:not([data-roallow]),
  .ro-lock select:not([data-roallow]){opacity:.6;cursor:default}
  .ro-lock [contenteditable="true"],.ro-lock [contenteditable=""]{opacity:.88}
  