/* ============================================================================
   PrintFlow theme — design tokens + app shell, layered over Bootstrap 5.3.
   Derived from concept/dashboard-concept-flow.html. Shell classes are pf-*
   prefixed so they never collide with Bootstrap (.nav, .brand, .links, ...).
   Dark mode is driven by data-theme (our CSS) + data-bs-theme (Bootstrap),
   both stamped on <html> by printflow.js.
   ============================================================================ */

:root{
  --font:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --navy:#062637;
  /* light */
  --bg:#f5fafc;
  --surface:#ffffff;
  --surface-2:#f4f8fa;
  --line:#eaf1f4;
  --line-2:#dde6ea;
  --text:#062637;
  --text-2:#597480;
  --text-3:#89a2ad;
  --brand:#0097dc;
  --brand-2:#00b4ee;
  --brand-deep:#0b5b84;
  --cyan-soft:#e7f6fd;
  --chip:#eaf1f5;
  --danger:#e5484d; --danger-soft:rgba(229,72,77,.10);
  --warn:#df8500;  --warn-soft:rgba(223,133,0,.12);
  --ok:#12a17a;    --ok-soft:rgba(18,161,122,.12);
  --river:#d7eefb;
  --shadow-sm:0 1px 2px rgba(6,38,55,.05);
  --shadow:0 12px 34px rgba(6,38,55,.07),0 2px 8px rgba(6,38,55,.04);
  --hero-bg:linear-gradient(180deg,#ecf7fd 0%,#ffffff 78%);
  --logo-ink:#062637;

  /* ---- Bridge PrintFlow tokens onto Bootstrap so every existing page adopts
     the palette, typography, radius and shadows without per-page rework. ---- */
  --bs-body-bg:var(--bg);
  --bs-body-color:var(--text);
  --bs-body-font-family:var(--font);
  --bs-border-color:var(--line-2);
  --bs-secondary-color:var(--text-2);
  --bs-tertiary-color:var(--text-3);
  --bs-tertiary-bg:var(--surface-2);
  --bs-emphasis-color:var(--text);
  --bs-primary:var(--brand);
  --bs-primary-rgb:0,151,220;
  --bs-link-color:var(--brand);
  --bs-link-color-rgb:0,151,220;
  --bs-link-hover-color:var(--brand-deep);
  --bs-border-radius:.7rem;
  --bs-border-radius-sm:.5rem;
  --bs-border-radius-lg:1rem;
  --bs-card-bg:var(--surface);
  --bs-card-border-color:var(--line);
  --bs-card-cap-bg:transparent;
}

html[data-theme="dark"]{
  --bg:#03141d;
  --surface:#0a2b3d;
  --surface-2:#0e3448;
  --line:rgba(255,255,255,.07);
  --line-2:rgba(255,255,255,.13);
  --text:#e9f2f7;
  --text-2:#a6c0cd;
  --text-3:#6f909f;
  --brand:#2ac4f3;
  --brand-2:#63d6ff;
  --brand-deep:#7fdcff;
  --cyan-soft:rgba(42,196,243,.13);
  --chip:rgba(255,255,255,.08);
  --danger:#ff6b72; --danger-soft:rgba(255,107,114,.13);
  --warn:#f2b34a;   --warn-soft:rgba(242,179,74,.14);
  --ok:#37cf90;     --ok-soft:rgba(55,207,144,.14);
  --river:#0e3a52;
  --shadow-sm:0 1px 2px rgba(0,0,0,.3);
  --shadow:0 18px 44px rgba(0,0,0,.42),0 2px 10px rgba(0,0,0,.3);
  --hero-bg:linear-gradient(180deg,#083149 0%,#04151e 82%);
  --logo-ink:#ffffff;

  --bs-primary-rgb:42,196,243;
  --bs-link-color-rgb:42,196,243;
  --bs-emphasis-color:#ffffff;
  --bs-tertiary-bg:var(--surface-2);
}

/* ---- Daylight island ----
   Puts the light palette back over one subtree of a dark page. Used by the press wall cards: the board
   itself stays dark, because a full-screen white panel is glare in a workshop and that is why
   layout/bare hard-codes the theme — but a press card is mostly colour swatches, and a Fiery's own
   cyan, magenta and yellow on a near-black surface wash out at exactly the distance the board is meant
   to be read from.

   The values repeat the light ones above rather than referring to them, because a custom property has
   no way to fall back to its :root value once html[data-theme="dark"] has overridden it. If the light
   palette changes, this changes with it — the two blocks are the same palette written twice. Only the
   tokens a daylight panel actually uses are listed; --bg is deliberately not among them, since the page
   behind the panel is still the dark one. */
.pf-daylight{
  --surface:#ffffff; --surface-2:#f4f8fa;
  --line:#eaf1f4; --line-2:#dde6ea;
  --text:#062637; --text-2:#597480; --text-3:#89a2ad;
  --brand:#0097dc; --brand-2:#00b4ee; --brand-deep:#0b5b84;
  --cyan-soft:#e7f6fd; --chip:#eaf1f5;
  --danger:#e5484d; --danger-soft:rgba(229,72,77,.10);
  --warn:#df8500;  --warn-soft:rgba(223,133,0,.12);
  --ok:#12a17a;    --ok-soft:rgba(18,161,122,.12);
  --shadow-sm:0 1px 2px rgba(6,38,55,.05);
  --shadow:0 12px 34px rgba(6,38,55,.07),0 2px 8px rgba(6,38,55,.04);
  color:var(--text);
}

/* ---- Base ---- */
body{
  font-family:var(--font);background:var(--bg);color:var(--text);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  /* Belt-and-braces for phones: nothing below should exceed the viewport, but one over-wide table on
     one page should scroll that table, not send the whole document sideways. `clip` rather than
     `hidden` — `hidden` would make <body> a scroll container and break the sticky nav. */
  overflow-x:clip;
}
.num,[data-count]{font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
::selection{background:var(--cyan-soft)}
.pf-icon{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.85;
  stroke-linecap:round;stroke-linejoin:round;flex:none}
/* One row of a list printed a line each (a sign's materials): a long row wraps under its own start, so it
   can't be read as the next one. */
.pf-hang{display:block;padding-left:1.25em;text-indent:-1.25em}

/* A few Bootstrap component nudges so inner pages feel on-brand. */
.btn-primary{
  --bs-btn-bg:var(--brand);--bs-btn-border-color:var(--brand);
  --bs-btn-hover-bg:var(--brand-deep);--bs-btn-hover-border-color:var(--brand-deep);
  --bs-btn-active-bg:var(--brand-deep);--bs-btn-active-border-color:var(--brand-deep);
  --bs-btn-disabled-bg:var(--brand);--bs-btn-disabled-border-color:var(--brand);
}
.btn-outline-primary{
  --bs-btn-color:var(--brand);--bs-btn-border-color:var(--brand);
  --bs-btn-hover-bg:var(--brand);--bs-btn-hover-border-color:var(--brand);
  --bs-btn-active-bg:var(--brand);--bs-btn-active-border-color:var(--brand);
}
/* Bootstrap compiles the pill's selected fill to a literal blue rather than reading --bs-primary, so
   setting the primary above doesn't reach it. Tabs are a selection control like the buttons beside
   them and have to be the same brand colour. */
.nav-pills{
  --bs-nav-pills-link-active-bg:var(--brand);
  --bs-nav-pills-link-active-color:#fff;
  --bs-nav-link-color:var(--text-2);
  --bs-nav-link-hover-color:var(--brand);
}
/* Same story as the pills: Bootstrap compiles --bs-pagination-active-bg to a literal #0d6efd
   instead of reading --bs-primary, so the selected page number on every paged list came out
   Bootstrap blue against a brand-blue page. */
.pagination{
  --bs-pagination-active-bg:var(--brand);
  --bs-pagination-active-border-color:var(--brand);
  --bs-pagination-active-color:#fff;
  --bs-pagination-color:var(--text-2);
  --bs-pagination-hover-color:var(--brand);
  --bs-pagination-focus-color:var(--brand);
  --bs-pagination-focus-box-shadow:0 0 0 .2rem var(--cyan-soft);
  --bs-pagination-border-color:var(--line);
  --bs-pagination-hover-border-color:var(--line);
}
.card{box-shadow:var(--shadow-sm);border-color:var(--line)}
.shadow-sm{box-shadow:var(--shadow-sm)!important}
.form-control:focus,.form-select:focus{
  border-color:var(--brand);box-shadow:0 0 0 .2rem var(--cyan-soft)
}
a{color:var(--brand)}

/* ============ TOP NAV (shell) ============ */
/* The height of the pinned chrome, in one place. --pf-nav-h sizes the bar itself and is what the press
   alert pins underneath (see the press bar, far below); --pf-chrome-h is the two of them together and is
   re-measured by printflow.js whenever the alert appears, wraps or is dismissed, so anything further
   down a page that sticks to the top can stop clear of whatever the chrome currently is. */
:root{--pf-nav-h:66px;--pf-chrome-h:var(--pf-nav-h)}

.pf-nav{
  position:sticky;top:0;z-index:1030;
  display:flex;align-items:center;gap:26px;
  padding:0 34px;height:var(--pf-nav-h);
  background:color-mix(in srgb,var(--surface) 82%,transparent);
  backdrop-filter:saturate(180%) blur(12px);
  -webkit-backdrop-filter:saturate(180%) blur(12px);
  border-bottom:1px solid var(--line);
}
.pf-nav a{text-decoration:none}
.pf-brand{display:flex;align-items:center;gap:10px;flex:0 1 auto;min-width:0}
.pf-brand .pf-logo{height:25px;width:auto;display:block}
.pf-brand .lw{fill:var(--logo-ink)} .pf-brand .lm{fill:var(--brand)}

/* Mobile menu button and the mark-only brand: both sit out until the drawer breakpoint below. */
.pf-burger{display:none;place-items:center;width:38px;height:38px;flex:none;padding:0;
  border:1px solid transparent;border-radius:10px;background:transparent;color:var(--text-2);
  cursor:pointer;transition:background .15s,color .15s}
.pf-burger:hover{background:var(--surface-2);color:var(--text)}
.pf-burger .pf-icon{width:22px;height:22px}
.pf-burger .pf-burger-x{display:none}
.pf-burger[aria-expanded="true"] .pf-burger-bars{display:none}
.pf-burger[aria-expanded="true"] .pf-burger-x{display:block}
.pf-brand .pf-mark{display:none;height:22px;width:auto}
.pf-brand .pf-mark path{fill:var(--brand)}
/* display:none at every other width so the scrim can never appear over a desktop page; the drawer
   breakpoint switches it to block and drives it with visibility/opacity so it can transition. */
.pf-navscrim{display:none;position:absolute;top:100%;left:0;width:100vw;height:100vh;height:100dvh;
  z-index:1;background:rgba(6,38,55,.45);opacity:0;visibility:hidden;
  transition:opacity .22s ease,visibility .22s ease}
.pf-nav.pf-navopen .pf-navscrim{opacity:1;visibility:visible}

.pf-links{display:flex;align-items:center;gap:3px}
.pf-links .pf-link{
  position:relative;padding:8px 14px;border-radius:9px;color:var(--text-2);
  font-weight:600;font-size:13.5px;white-space:nowrap;cursor:pointer;
  display:inline-flex;align-items:center;gap:5px;
  transition:color .15s,background .15s;background:transparent;border:0;
}
.pf-links .pf-link:hover{color:var(--text);background:var(--surface-2)}
.pf-links .pf-link.active{color:var(--text)}
.pf-links .pf-link.active::after{
  content:"";position:absolute;left:14px;right:14px;bottom:-1px;height:2.5px;
  border-radius:3px;background:linear-gradient(90deg,var(--brand),var(--brand-2))
}
/* top-level parents open their submenu on hover / keyboard focus */
.pf-navitem{position:relative;display:inline-flex;align-items:center}
.pf-navitem::after{content:"";position:absolute;top:100%;left:0;right:0;height:10px}
.pf-submenu{
  position:absolute;top:calc(100% + 8px);left:0;min-width:210px;
  background:var(--surface);border:1px solid var(--line);border-radius:14px;
  box-shadow:var(--shadow);padding:6px;z-index:1040;
  opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .16s,transform .16s,visibility .16s;
}
.pf-navitem:hover>.pf-submenu,.pf-navitem:focus-within>.pf-submenu{opacity:1;visibility:visible;transform:none}
.pf-subitem{
  display:flex;align-items:center;gap:11px;padding:9px 11px;border-radius:9px;
  font-size:13.5px;font-weight:500;color:var(--text);white-space:nowrap;cursor:pointer;
}
.pf-subitem:hover{background:var(--surface-2);color:var(--text)}
.pf-subitem .pf-icon{width:17px;height:17px;color:var(--text-3)}
.pf-subitem.disabled{color:var(--text-3);cursor:default;pointer-events:none}
/* Groups a submenu without adding a heading — the till entries are transactions too, just not paperwork. */
.pf-subsep{height:1px;background:var(--line);margin:5px 8px}
/* A submenu's link to its own heading's page. On desktop the heading itself goes there; in the drawer the
   heading opens and closes its section instead, so this is the only way through. */
.pf-drawer-only{display:none}

.pf-sp{flex:1}
/* flex:none here and on the buttons: when the bar runs out of room the brand gives way (it can, it has
   a mark-only form), never the tap targets — a half-width bell is worse than no wordmark. */
.pf-actions{display:flex;align-items:center;gap:6px;flex:none}

.pf-iconbtn{
  display:grid;place-items:center;width:38px;height:38px;border-radius:10px;flex:none;
  color:var(--text-2);cursor:pointer;position:relative;background:transparent;
  border:1px solid transparent;transition:.15s;text-decoration:none;
}
.pf-iconbtn:hover,.pf-iconbtn.show,.pf-iconbtn[aria-expanded="true"]{background:var(--surface-2);color:var(--text)}

/* Row-action size: the 38px button is built for the chrome, and three of them stacked in a table
   row make it scroll. 30px keeps the tap target usable without setting the row height. */
.pf-iconbtn-sm{width:30px;height:30px;border-radius:8px}
.pf-iconbtn-sm .pf-icon{width:16px;height:16px}
/* Destructive actions carry their colour all the time, not only on hover: the point of the red is to
   be read before the click, and an icon that only declares itself once the pointer is on it has
   already missed its moment. */
.pf-iconbtn-danger{color:var(--danger)}
.pf-iconbtn-danger:hover{background:var(--danger-soft);color:var(--danger)}
/* A form wrapping a single icon button must not add layout of its own, or the row springs apart. */
.pf-actions form{display:contents}
/* Reserves the width of an action a given row does not have, so a column of icons stays in line
   instead of shuffling sideways. Not a .pf-iconbtn: that one lights up and shows a pointer on hover,
   which on an empty box is an affordance promising something that isn't there. */
.pf-iconbtn-gap{width:30px;height:30px;flex:none;pointer-events:none}
/* The badge carries the unread count, so it is a pill sized for one or two digits (the template
   folds anything past 99 into "99+"), not the 7px dot it started life as: a 9px digit inside a
   7px box spilled above and below the red and read as a clipped smudge on a light bar. It sits on
   the bell's top-right shoulder, the surface-coloured ring cutting it out of the icon beneath. */
.pf-iconbtn .pf-dot{
  position:absolute;top:4px;right:4px;min-width:16px;height:16px;padding:0 3px;border-radius:99px;
  background:var(--danger);border:2px solid var(--surface);
  font-size:10px;line-height:1;font-weight:700;color:#fff;display:grid;place-items:center;
}

/* collapsible search */
.pf-search{display:flex;align-items:center;margin:0}
.pf-search-input{
  width:0;min-width:0;opacity:0;height:38px;padding:0;margin:0;
  border:1px solid transparent;border-radius:11px;background:var(--surface-2);
  font:inherit;color:var(--text);pointer-events:none;
  transition:width .24s ease,opacity .18s,padding .18s,margin .18s;
}
.pf-search-input::placeholder{color:var(--text-3)}
.pf-search.open .pf-search-input{width:250px;opacity:1;padding:0 13px;margin-right:6px;border-color:var(--line);pointer-events:auto}
.pf-search.open .pf-search-input:focus{border-color:var(--brand);box-shadow:0 0 0 3px var(--cyan-soft);outline:0}

/* avatar */
.pf-avatar{
  width:36px;height:36px;border-radius:50%;display:grid;place-items:center;flex:none;
  color:#fff;font-weight:700;font-size:12px;border:0;cursor:pointer;
  background:linear-gradient(135deg,#0097dc,#075a86);
}

/* Bootstrap dropdown menus restyled to the concept "menu" look */
.pf-nav .dropdown-menu{
  --bs-dropdown-bg:var(--surface);
  --bs-dropdown-border-color:var(--line);
  --bs-dropdown-link-color:var(--text);
  --bs-dropdown-link-hover-bg:var(--surface-2);
  --bs-dropdown-link-hover-color:var(--text);
  --bs-dropdown-link-active-bg:var(--surface-2);
  --bs-dropdown-link-active-color:var(--text);
  border-radius:14px;box-shadow:var(--shadow);padding:6px;margin-top:10px;min-width:200px;
  font-size:13.5px;
}
.pf-nav .dropdown-menu .dropdown-item{border-radius:9px;padding:9px 11px;font-weight:500;display:flex;align-items:center;gap:11px}
.pf-nav .dropdown-menu .dropdown-item.disabled{color:var(--text-3)}
.pf-nav .dropdown-menu .dropdown-item .pf-icon{width:17px;height:17px;color:var(--text-3)}
.pf-nav .dropdown-header{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);padding:8px 11px 5px}
.pf-menu-badge{margin-left:auto;font-size:10px;font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;color:var(--text-3);background:var(--chip);border-radius:6px;padding:2px 6px}

/* notifications dropdown width + wrapping */
.pf-notif-menu{min-width:20rem!important;max-width:22rem}
.pf-notif-menu .dropdown-item{display:block!important}

/* page container tuned to the concept width */
main.pf-wrap{max-width:1280px;margin:0 auto;padding:30px 34px 60px}

/* ============ DASHBOARD PAGE ============ */
.pf-head{display:flex;align-items:baseline;justify-content:space-between;gap:20px;margin-bottom:20px;flex-wrap:wrap}
.pf-head h1{margin:0;font-size:27px;font-weight:600;letter-spacing:-.025em}
.pf-head .sub{margin:0;color:var(--text-2);font-size:14px}

/* flow hero */
.pf-flow{position:relative;overflow:hidden;background:var(--hero-bg);border:1px solid var(--line);border-radius:22px;box-shadow:var(--shadow);padding:24px 20px 8px;margin-bottom:22px}
.pf-swoosh{position:absolute;right:-40px;top:-30px;width:360px;opacity:.06;pointer-events:none}
.pf-swoosh path{fill:var(--brand)}
.pf-cap{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 14px 4px;flex-wrap:wrap}
.pf-cap h2{margin:0;font-size:13px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:var(--text-2)}
.pf-cap .legend{font-size:12.5px;color:var(--text-2)}
.pf-cap .legend b{color:var(--brand);font-weight:700}
/* Flow stations. Ordinary HTML text, so the figures stay legible at any width and honour the reader's
   font size — the previous SVG-text version scaled with a 1200-unit viewBox and became unreadable on a
   phone. Five equal columns put each station's centre at 10/30/50/70/90%, which is exactly where the
   river below draws its nodes. */
.pf-flowboard{position:relative}
.pf-stations{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:4px}
.pf-station{display:flex;flex-direction:column;align-items:center;gap:3px;min-width:0;text-align:center;
  padding:8px 6px 10px;border-radius:14px;text-decoration:none;color:inherit;transition:background .13s}
.pf-station:hover{background:color-mix(in srgb,var(--brand) 7%,transparent)}
.pf-station:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.pf-station .c{font-size:34px;font-weight:600;letter-spacing:-.02em;line-height:1.05;color:var(--text);
  font-variant-numeric:tabular-nums}
.pf-station .l{font-size:11.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--text-2)}
.pf-station .s{font-size:12px;font-weight:600;color:var(--text-2)}
.pf-station:hover .c,.pf-station:hover .l{color:var(--brand)}
.pf-flowriver{display:block;width:100%;height:auto}
.pf-fl-tick{stroke:var(--line-2);stroke-width:1}
.pf-flow-river{stroke:var(--river);stroke-width:9;fill:none;stroke-linecap:round}
.pf-flow-line{stroke:url(#pf-flowgrad);stroke-width:2.5;fill:none;opacity:.95}
.pf-flow-dots{stroke:var(--brand);stroke-width:4.5;stroke-linecap:round;stroke-dasharray:.01 27;fill:none;animation:pf-flowmove 3.4s linear infinite}
@keyframes pf-flowmove{to{stroke-dashoffset:-27}}
.pf-fl-halo{fill:var(--brand);opacity:.13}
.pf-fl-node{fill:var(--surface);stroke:url(#pf-flowgrad);stroke-width:2.6}

/* metric ribbon */
.pf-ribbon{display:flex;background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow);overflow:hidden;margin-bottom:22px}
.pf-metric{flex:1;padding:18px 22px;border-left:1px solid var(--line)}
.pf-metric:first-child{border-left:0}
.pf-metric .l{font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--text)}
.pf-metric .v{font-size:29px;font-weight:300;letter-spacing:-.02em;margin-top:8px;line-height:1}
.pf-metric .d{font-size:12px;color:var(--text-2);margin-top:6px}
.pf-metric.warnv .v,.pf-metric.warnv .l{color:var(--warn)}
.pf-metric.dangerv .v,.pf-metric.dangerv .l{color:var(--danger)}
/* The tiles are links now (each opens the list of what it counted), so undo the anchor defaults and give
   them a hover instead — a number that responds to the pointer is the only cue that it can be opened. */
a.pf-metric{display:block;text-decoration:none;color:inherit;transition:background .13s}
a.pf-metric:hover{background:var(--surface-2)}
a.pf-metric:hover .l{text-decoration:underline}
a.pf-metric:focus-visible{outline:2px solid var(--brand);outline-offset:-2px}

/* receivable figures on the customer's Billing panel — the ribbon's l/v/d idiom at panel scale, so a
   figure reads the same here as it does on a dashboard tile. .late is the whole point of the pair:
   outstanding is the account, overdue is the phone call, and only one of them is red. */
.pf-ar{display:flex;flex-wrap:wrap;gap:12px}
.pf-ar-fig{flex:1 1 190px;border:1px solid var(--line);border-radius:12px;padding:10px 14px}
.pf-ar-fig .l{font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--text-2)}
.pf-ar-fig .v{font-size:26px;font-weight:300;letter-spacing:-.02em;line-height:1;margin-top:6px;
  font-variant-numeric:tabular-nums}
.pf-ar-fig .d{font-size:12px;color:var(--text-2);margin-top:5px}
.pf-ar-fig .d a{color:inherit;text-decoration:none}
.pf-ar-fig .d a:hover{text-decoration:underline}
/* Column headings inside the merged Account panel. Small caps rather than a second card header: the
   two halves are one panel now, and giving each a header would rebuild the divide the merge removed. */
.pf-people-h{font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  color:var(--text-2);padding-bottom:8px;margin-bottom:12px;border-bottom:1px solid var(--line)}
.pf-ar-fig.late{border-color:var(--danger);background:var(--danger-soft)}
.pf-ar-fig.late .l,.pf-ar-fig.late .v{color:var(--danger)}

/* grid + cards */
/* minmax(0,…), never a bare fr. A bare `1fr` is minmax(auto,1fr): the column will not get narrower than the
   longest line inside it that cannot break, and the activity feed's detail line is nowrap by design. So on a
   phone one long entry ("Supplier order cancelled — garments are back on the buying list · 18 Sep…") set the
   width of the single column and all four cards ran off the right of the screen; on desktop the same line
   quietly widened the right column at the focus list's expense. Held to its share, the column makes those
   lines truncate instead. Where nothing was too long for its column, nothing moves. */
.pf-grid{display:grid;grid-template-columns:minmax(0,1.62fr) minmax(0,1fr);gap:22px;align-items:start}
.pf-col{display:flex;flex-direction:column;gap:22px;min-width:0}
.pf-card{background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow)}
.pf-card-h{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:18px 22px 14px}
.pf-card-h h3{margin:0;font-size:16px;font-weight:600;letter-spacing:-.01em}
.pf-card-h .meta{font-size:12.5px;color:var(--text-3)}
.pf-card-h .link{font-size:12.5px;font-weight:600;color:var(--brand);display:inline-flex;align-items:center;gap:4px;text-decoration:none}
.pf-card-h .link .pf-icon{width:14px;height:14px;stroke-width:2.3}

/* focus list */
.pf-focus{padding:0 8px 8px}
.pf-frow{display:grid;grid-template-columns:10px minmax(0,1.5fr) minmax(0,1.1fr) 108px 34px;align-items:center;gap:14px;padding:13px 14px;border-radius:12px;transition:background .13s}
.pf-frow:hover{background:var(--surface-2)}
.pf-frow+.pf-frow{box-shadow:0 -1px 0 var(--line)}
.pf-udot{width:8px;height:8px;border-radius:50%;background:transparent}
.pf-udot.over{background:var(--danger)} .pf-udot.today{background:var(--warn)}
/* A name or title with no spaces in it (a filename-style title) breaks rather than spilling out of the row.
   break-word, not anywhere: anywhere also lets the flex row squeeze the "At risk"/"Reprint" flags into
   breaking mid-word, which is why the item text is the one given min-width:0. */
.pf-fmain{overflow-wrap:break-word}
.pf-csub>span:first-child{min-width:0}
.pf-fmain b{font-weight:600;font-size:14px}
.pf-jn{color:var(--brand);font-weight:700;font-variant-numeric:tabular-nums;margin-right:8px;text-decoration:none}
.pf-jn:hover{text-decoration:underline}
.pf-csub{color:var(--text-2);font-size:12.5px;display:flex;align-items:center;gap:7px;margin-top:2px}
.pf-fmini{display:inline-flex;align-items:center;gap:3px;color:var(--text-3);font-size:11px;font-weight:600}
.pf-stage{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;font-weight:500;color:var(--text-2)}
.pf-stage .s{width:7px;height:7px;border-radius:50%;background:var(--text-3);opacity:.55}
.pf-due{font-size:12.5px;font-weight:600;text-align:right}
.pf-due.over{color:var(--danger)} .pf-due.today{color:var(--warn)} .pf-due.soon{color:var(--text-3)} .pf-due.none{color:var(--text-3)}
.pf-av{border-radius:50%;display:inline-grid;place-items:center;background:var(--chip);color:var(--text-2);font-weight:700;font-size:10.5px;width:28px;height:28px;flex:none}
.pf-empty{padding:16px 22px;color:var(--text-3);font-size:13px}

/* production load */
.pf-load{padding:6px 22px 18px}
.pf-lrow{display:flex;align-items:center;gap:13px;padding:8px 0}
.pf-lrow+.pf-lrow{box-shadow:0 -1px 0 var(--line)}
.pf-lrow.idle{opacity:.5}
.pf-lrow .n{flex:0 0 150px;min-width:0;display:flex;align-items:baseline;gap:8px}
.pf-lrow .nm{font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-lrow .od{flex:none;font-size:10.5px;font-weight:700;color:var(--danger);white-space:nowrap}
.pf-lrow .track{flex:1 1 auto;height:5px;border-radius:999px;background:var(--surface-2);overflow:hidden}
.pf-lrow .fill{height:100%;border-radius:999px;background:linear-gradient(90deg,var(--brand),var(--brand-2))}
.pf-lrow .pct{flex:0 0 36px;text-align:right;font-size:11.5px;font-weight:600;color:var(--text-3);font-variant-numeric:tabular-nums}
.pf-lrow .c{flex:0 0 24px;font-size:13px;font-weight:700;text-align:right;font-variant-numeric:tabular-nums}

/* generic list (awaiting) */
.pf-list{padding:4px 8px 8px}
.pf-item{display:flex;align-items:center;gap:13px;padding:12px 14px;border-radius:12px;transition:background .13s}
.pf-item:hover{background:var(--surface-2)}
.pf-item+.pf-item{box-shadow:0 -1px 0 var(--line)}
.pf-item .who{min-width:0;overflow-wrap:break-word}
.pf-item .who b{font-weight:600;font-size:13.5px}
.pf-item .who .s{color:var(--text-3);font-size:12px}
.pf-item .right{margin-left:auto;text-align:right;white-space:nowrap}
.pf-item .wait{font-size:12px;font-weight:600;color:var(--text-2)}
.pf-item .wait.cool{color:var(--text-3)}
.pf-item .r2{color:var(--text-3);font-size:11.5px}

/* activity feed */
.pf-feed{padding:4px 10px 10px}
.pf-fitem{display:flex;gap:12px;align-items:flex-start;padding:9px 8px;border-radius:10px}
.pf-fitem+.pf-fitem{box-shadow:0 -1px 0 var(--line)}
.pf-fitem .ic{width:26px;height:26px;border-radius:50%;flex:none;display:grid;place-items:center;background:var(--chip);color:var(--text-2)}
.pf-fitem .ic .pf-icon{width:14px;height:14px}
.pf-fitem .fb{min-width:0;flex:1}
.pf-fitem .hd{display:flex;align-items:baseline;gap:8px;min-width:0}
.pf-fitem .num{font-size:13px;font-weight:700;color:var(--brand);text-decoration:none;font-variant-numeric:tabular-nums;white-space:nowrap;flex:none}
.pf-fitem .num:hover{text-decoration:underline}
.pf-fitem .cl{font-size:12.5px;color:var(--text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.pf-fitem .ttl{font-size:13px;font-weight:600;color:var(--text);min-width:0;overflow-wrap:break-word}
.pf-fitem .t{font-size:11.5px;color:var(--text-3);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ============================================================================
   INNER PAGES — lift the standard Bootstrap components used on every non-
   dashboard screen (lists, forms, detail views, settings) to the dashboard's
   clean, flowy feel. Scoped to .pf-wrap so the top nav is untouched, and built
   entirely from the tokens above. The dashboard uses .pf-* classes, so these
   .card / .table / .badge rules never collide with it.
   ============================================================================ */

/* page title — mirrors the dashboard .pf-head heading (h1.h3 is inner-page only) */
.pf-wrap h1.h3{font-size:26px;font-weight:600;letter-spacing:-.022em;color:var(--text)}

/* cards → floaty, generously rounded */
.pf-wrap .card{border:1px solid var(--line);border-radius:18px;background:var(--surface);
  box-shadow:var(--shadow);overflow:hidden}
.pf-wrap .card.shadow-sm{box-shadow:var(--shadow)!important}
.pf-wrap .card-header{background:transparent;border-bottom:1px solid var(--line);
  padding:15px 20px;font-size:15px;font-weight:600;color:var(--text);letter-spacing:-.01em}
.pf-wrap .card-body{padding:20px}

/* tables → airy, uppercase micro-label header, no grey block */
.pf-wrap .table{--bs-table-bg:transparent;color:var(--text);margin-bottom:0;border-color:var(--line)}
.pf-wrap .table-light{--bs-table-bg:transparent;--bs-table-color:var(--text-3)}
.pf-wrap .table>thead>tr>th{--bs-table-bg:transparent;background:transparent;color:var(--text-3);
  font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  padding:12px 16px;border-bottom:1px solid var(--line-2);white-space:nowrap}
.pf-wrap .table>tbody>tr>td{padding:13px 16px;border-color:var(--line);vertical-align:middle}
.pf-wrap .table>tbody>tr:last-child>td{border-bottom:0}
.pf-wrap .table-hover>tbody>tr:hover>*{--bs-table-accent-bg:transparent;background:var(--surface-2)}

/* status chips → soft, pill-shaped */
.pf-wrap .badge{font-weight:600;font-size:.72rem;letter-spacing:.01em;padding:.36em .72em;
  border-radius:999px;line-height:1.15}
.pf-wrap .badge.text-bg-success{background:var(--ok-soft)!important;color:var(--ok)!important}
.pf-wrap .badge.text-bg-danger{background:var(--danger-soft)!important;color:var(--danger)!important}
.pf-wrap .badge.text-bg-warning{background:var(--warn-soft)!important;color:var(--warn)!important}
.pf-wrap .badge.text-bg-secondary{background:var(--chip)!important;color:var(--text-2)!important}
.pf-wrap .badge.text-bg-light{background:var(--surface-2)!important;color:var(--text-2)!important}
.pf-wrap .badge.text-bg-info{background:var(--cyan-soft)!important;color:var(--brand-deep)!important}
.pf-wrap .badge.text-bg-primary{background:var(--cyan-soft)!important;color:var(--brand-deep)!important}
.pf-wrap .badge.bg-secondary{background:var(--chip)!important;color:var(--text-2)!important}

/* detail definition lists */
.pf-wrap dl.row dt{color:var(--text-3);font-weight:600;font-size:13px;padding:6px 0}
.pf-wrap dl.row dd{color:var(--text);font-size:14px;padding:6px 0;margin-bottom:0}

/* list-group timelines (activity fragment) */
.pf-wrap .list-group-flush>.list-group-item{background:transparent;border-color:var(--line);
  padding:13px 20px;color:var(--text)}
.pf-wrap .list-group-flush>.list-group-item:hover{background:var(--surface-2)}

/* clickable cards (displays and reports landings) → hover lift */
.pf-wrap a.card{
  transition:transform .16s ease,box-shadow .16s ease,border-color .16s ease}
.pf-wrap a.card{color:inherit}
.pf-wrap a.card:hover{transform:translateY(-3px);
  border-color:color-mix(in srgb,var(--brand) 42%,var(--line));
  box-shadow:0 22px 46px rgba(6,38,55,.12),0 4px 12px rgba(6,38,55,.06)!important}
html[data-theme="dark"] .pf-wrap a.card:hover{
  box-shadow:0 22px 46px rgba(0,0,0,.5),0 4px 12px rgba(0,0,0,.34)!important}

/* ============ SETTINGS LANDING — categorised, filterable icon-tile grid ============ */
.pf-wrap .pf-set-filter{display:flex;align-items:center;gap:9px;background:var(--surface);
  border:1px solid var(--line-2);border-radius:11px;padding:0 13px;height:42px;max-width:360px;margin-bottom:24px}
.pf-wrap .pf-set-filter:focus-within{border-color:var(--brand);box-shadow:0 0 0 3px var(--cyan-soft)}
.pf-wrap .pf-set-filter .pf-icon{width:18px;height:18px;color:var(--text-3);flex:none}
.pf-wrap .pf-set-filter input{border:0;outline:0;background:transparent;font:inherit;font-size:14px;
  color:var(--text);width:100%;min-width:0}
.pf-wrap .pf-set-filter input::placeholder{color:var(--text-3)}
.pf-set-sec{margin-bottom:22px}
.pf-set-sec-h{font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  color:var(--text-3);margin:0 2px 10px}
.pf-set-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(238px,1fr));gap:11px}
.pf-set-tile{display:flex;align-items:center;gap:13px;background:var(--surface);border:1px solid var(--line);
  border-radius:14px;padding:12px 14px;text-decoration:none;color:inherit;
  transition:transform .16s ease,box-shadow .16s ease,border-color .16s ease}
.pf-set-tile:hover{transform:translateY(-3px);border-color:color-mix(in srgb,var(--brand) 42%,var(--line));
  box-shadow:0 18px 38px rgba(6,38,55,.10),0 3px 10px rgba(6,38,55,.05)}
html[data-theme="dark"] .pf-set-tile:hover{box-shadow:0 18px 38px rgba(0,0,0,.5),0 3px 10px rgba(0,0,0,.34)}
.pf-set-tile:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.pf-set-ic{flex:none;width:40px;height:40px;border-radius:11px;background:var(--cyan-soft);color:var(--brand);
  display:grid;place-items:center}
.pf-set-ic .pf-icon{width:21px;height:21px;stroke-width:1.7}
.pf-set-tx{min-width:0}
.pf-set-tt{display:block;font-size:14px;font-weight:600;color:var(--text);line-height:1.2;letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pf-set-td{display:block;font-size:12px;color:var(--text-2);margin-top:2px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pf-set-empty{display:none;color:var(--text-3);font-size:13.5px;padding:4px 2px}

/* ============ SETTINGS → AUTOMATIC EMAILS — a row per email, a tick box per recipient ============
   Each box is a real checkbox inside a pill, so the page reads as tick boxes and a ticked recipient still stands
   out at a glance down the list. Weight never changes on tick, so toggling doesn't shuffle the row. */
.pf-wrap .ae-list{background:var(--surface);border:1px solid var(--line);border-radius:14px;overflow:hidden}
.pf-wrap .ae-row{display:grid;grid-template-columns:minmax(12rem,16rem) minmax(0,1fr);gap:10px 26px;padding:16px 18px}
.pf-wrap .ae-row+.ae-row{border-top:1px solid var(--line)}
.pf-wrap .ae-title{font-size:14.5px;font-weight:600;color:var(--text);letter-spacing:-.01em;line-height:1.3}
.pf-wrap .ae-when{font-size:12.5px;color:var(--text-2);margin-top:3px;line-height:1.4}
.pf-wrap .ae-off{font-size:12px;color:var(--warn);margin-top:6px}
.pf-wrap .ae-who{display:flex;flex-direction:column;gap:9px;min-width:0}
.pf-wrap .ae-set{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.pf-wrap .ae-set-h{flex:none;width:4.9rem;font-size:10.5px;font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;color:var(--text-3)}
.pf-wrap .ae-chip{display:inline-flex;align-items:center;gap:7px;margin:0;padding:4px 11px 4px 8px;
  border:1px solid var(--line-2);border-radius:999px;background:var(--surface);color:var(--text-2);
  font-size:13px;font-weight:500;line-height:1.35;cursor:pointer;user-select:none;
  transition:background .12s ease,border-color .12s ease,color .12s ease}
.pf-wrap .ae-chip .form-check-input{margin:0;flex:none;cursor:pointer}
.pf-wrap .ae-chip:hover{border-color:color-mix(in srgb,var(--brand) 45%,var(--line-2))}
.pf-wrap .ae-chip:has(input:checked){background:var(--cyan-soft);color:var(--brand-deep);
  border-color:color-mix(in srgb,var(--brand) 55%,var(--line-2))}
.pf-wrap .ae-chip:has(input:focus-visible){outline:2px solid var(--brand);outline-offset:2px}
.pf-wrap .ae-note{flex:1 1 15rem;min-width:0;font-size:12px;color:var(--text-2);line-height:1.4}
.pf-wrap .ae-bell{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-3);
  padding-left:calc(4.9rem + 6px)}
.pf-wrap .ae-bell .pf-icon{width:14px;height:14px;flex:none}
.pf-wrap .ae-save{position:sticky;bottom:0;z-index:5;display:flex;gap:8px;padding:14px 0 16px;
  background:linear-gradient(to top,var(--bg) 72%,transparent)}
@media (max-width:760px){
  .pf-wrap .ae-row{grid-template-columns:minmax(0,1fr);padding:14px}
  .pf-wrap .ae-set-h{width:100%}
  .pf-wrap .ae-bell{padding-left:0}
}

/* forms */
.pf-wrap .form-label{font-weight:500;font-size:13.5px;color:var(--text-2);margin-bottom:5px}
.pf-wrap .input-group-text{background:var(--surface-2);border-color:var(--line-2);color:var(--text-2)}

/* profile → appearance: Light / Dark / Auto segmented radio-tiles */
.pf-wrap .pf-themeseg{display:flex;gap:10px;flex-wrap:wrap;max-width:460px}
.pf-wrap .pf-themeopt{flex:1 1 120px;position:relative;display:flex;flex-direction:column;align-items:center;
  gap:8px;padding:16px 12px;border:1px solid var(--line-2);border-radius:14px;cursor:pointer;
  background:var(--surface);color:var(--text-2);text-align:center;transition:border-color .15s,color .15s,background .15s}
.pf-wrap .pf-themeopt:hover{border-color:var(--brand);color:var(--text)}
.pf-wrap .pf-themeopt input{position:absolute;opacity:0;width:0;height:0;pointer-events:none}
.pf-wrap .pf-themeopt svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.pf-wrap .pf-themeopt .t{font-size:13.5px;font-weight:600}
.pf-wrap .pf-themeopt:has(input:checked){border-color:var(--brand);background:var(--cyan-soft);color:var(--brand)}
.pf-wrap .pf-themeopt:has(input:focus-visible){box-shadow:0 0 0 3px var(--cyan-soft);border-color:var(--brand)}

/* buttons — keep Bootstrap radii (so input-group joins stay clean); firm up the rest */
.pf-wrap .btn{font-weight:600}
.pf-wrap .btn-outline-secondary{--bs-btn-color:var(--text-2);--bs-btn-border-color:var(--line-2);
  --bs-btn-hover-bg:var(--surface-2);--bs-btn-hover-border-color:var(--line-2);--bs-btn-hover-color:var(--text);
  --bs-btn-active-bg:var(--surface-2);--bs-btn-active-border-color:var(--line-2);--bs-btn-active-color:var(--text)}

/* alerts → rounded + softly tinted (covers flash banners + in-page alerts) */
.pf-wrap .alert{border-radius:14px;border:1px solid transparent;font-size:14px}
.pf-wrap .alert-success{background:var(--ok-soft);border-color:color-mix(in srgb,var(--ok) 22%,transparent);color:var(--text)}
.pf-wrap .alert-danger{background:var(--danger-soft);border-color:color-mix(in srgb,var(--danger) 22%,transparent);color:var(--text)}
.pf-wrap .alert-warning{background:var(--warn-soft);border-color:color-mix(in srgb,var(--warn) 24%,transparent);color:var(--text)}
.pf-wrap .alert-info{background:var(--cyan-soft);border-color:color-mix(in srgb,var(--brand) 22%,transparent);color:var(--text)}
.pf-wrap .alert-light{background:var(--surface-2);border-color:var(--line);color:var(--text-2)}

/* hairlines */
.pf-wrap hr{border-color:var(--line);opacity:1}

/* production & freight kanban boards — tokenised here (was a hard-coded <style>
   block duplicated in board.html/freight.html, which broke in dark mode). */
.pf-wrap .board-columns{display:flex;gap:14px;overflow-x:auto;padding-bottom:.5rem;align-items:flex-start}
.pf-wrap .board-column{flex:0 0 15rem;background:var(--surface-2);border:1px solid var(--line);
  border-radius:16px;padding:10px}
.pf-wrap .board-column>h2{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;
  color:var(--text-3);display:flex;justify-content:space-between;align-items:center;margin:2px 4px 10px}
.pf-wrap .board-dropzone{min-height:2rem}
.pf-wrap .board-column.drop-target{outline:2px dashed var(--brand);outline-offset:2px}
.pf-wrap .board-tile{background:var(--surface);border:1px solid var(--line);border-left:4px solid var(--text-3);
  border-radius:12px;padding:9px 11px;margin-bottom:9px;cursor:grab;font-size:.85rem;
  box-shadow:var(--shadow-sm);transition:box-shadow .13s ease}
.pf-wrap .board-tile:hover{box-shadow:var(--shadow)}
/* Held: stays in its column, but reads as parked rather than in-flight. */
.pf-wrap .board-tile.tile-held{border-left-color:var(--warn);border-style:dashed;opacity:.72}
.pf-wrap .board-tile.tile-overdue{border-left-color:var(--danger)}
.pf-wrap .board-tile.tile-today{border-left-color:var(--warn)}
/* Stalled: held AND past its date. Keeps the held tile's dashed edge so it still reads as parked, but
   takes the overdue red and full opacity — it has blown a commitment and shouldn't fade out. The
   hatching is what separates it from a plain overdue tile that nobody has explained. Must follow
   .tile-held/.tile-overdue to win on equal specificity. */
.pf-wrap .board-tile.tile-stalled{border-left-color:var(--danger);border-style:dashed;opacity:1;
  background-image:repeating-linear-gradient(135deg,transparent 0 7px,var(--danger-soft) 7px 14px)}
.pf-wrap .board-tile .tile-num{font-weight:700;text-decoration:none;color:var(--brand)}
.pf-wrap .board-tile .tile-title{font-size:.8rem;color:var(--text)}
.pf-wrap .board-tile .tile-meta{color:var(--text-2);font-size:.78rem}
.pf-wrap .board-tile .tile-status{color:var(--text-3);font-size:.75rem}
.pf-wrap .board-tile .tile-due{font-size:.8rem}
.pf-wrap .board-tile .tile-due.provisional{font-style:italic;color:var(--text-3)}
.pf-wrap .board-tile .tile-due.overdue{color:var(--danger);font-weight:600}
.pf-wrap .board-tile.dragging{opacity:.5}

/* Hold badges, shared by the board, the jobs list and the freight board so one state has one look.
   Deliberately not Bootstrap's text-bg-warning: that is already Reprint, and the two sit in the same
   cell on the jobs list. Stalled (held and past due) borrows the danger palette to match its tile. */
.pf-wrap .badge.pf-badge-hold{background:var(--chip);color:var(--text-2);border:1px solid var(--warn)}
.pf-wrap .badge.pf-badge-stalled{background:var(--danger-soft);color:var(--danger);
  border:1px solid var(--danger)}

/* Apparel procurement icons: truck = ordering, warehouse = receiving. Colour is the whole message, so it
   is named by meaning rather than by hue — and both are theme tokens, so they stay legible in dark mode.
   There is deliberately no grey: an icon is drawn only when it has something to say, so absence has exactly
   one meaning — nothing to do here. "Nothing ordered yet" is amber like "part ordered", because it is the
   same call to action and greying it would make the worse state the less visible one. */
.pf-garment-part{color:var(--warn)}
.pf-garment-done{color:var(--ok)}
.pf-garment-late{color:var(--danger)}

/* ============ LIST TOOLBAR (pflist) — sleek filter + multi-sort for list tables ============ */
.pf-listbar{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-bottom:12px;min-height:32px}
.pf-lb-group{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.pf-lb-label{font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--text-3)}
.pf-fchip{border:1px solid var(--line-2);background:transparent;color:var(--text-2);border-radius:999px;
  padding:4px 12px;font-size:12.5px;line-height:1.45;cursor:pointer;transition:.12s;font-family:inherit}
.pf-fchip:hover{border-color:var(--brand);color:var(--brand)}
.pf-fchip.on{background:var(--brand);border-color:var(--brand);color:#fff;font-weight:600}
.pf-sortlvl{display:inline-flex;align-items:stretch;border:1px solid var(--line-2);border-radius:8px;
  overflow:hidden;background:var(--surface)}
.pf-sortlvl select{border:0;background:transparent;color:var(--text);font-family:inherit;font-size:12.5px;
  padding:4px 8px;cursor:pointer;max-width:130px}
.pf-sortlvl select:focus{outline:0}
.pf-sortdir,.pf-sortdel{border:0;border-left:1px solid var(--line-2);background:transparent;color:var(--text-2);
  cursor:pointer;padding:3px 9px;font-size:12px;font-family:inherit}
.pf-sortdir:hover,.pf-sortdel:hover{background:var(--surface-2);color:var(--brand)}
.pf-sortadd{border:1px dashed var(--line-2);background:transparent;color:var(--text-2);border-radius:8px;
  padding:4px 12px;font-size:12.5px;cursor:pointer;font-family:inherit}
.pf-sortadd:hover{border-color:var(--brand);color:var(--brand)}
.pf-lb-count{font-size:12.5px;color:var(--text-3);font-variant-numeric:tabular-nums}
/* Drill-through chip: says why this list is short after arriving from a dashboard counter, and carries the
   one click that undoes it. Brand-coloured because it is the only narrowing the user did not set here. */
.pf-lb-focus{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--brand);
  color:var(--brand);border-radius:999px;padding:3px 6px 3px 12px;font-size:12.5px;font-weight:600}
.pf-lb-focusx{border:0;background:transparent;color:inherit;cursor:pointer;font-family:inherit;
  font-size:15px;line-height:1;padding:0 5px;border-radius:999px;opacity:.75}
.pf-lb-focusx:hover{opacity:1;background:color-mix(in srgb,var(--brand) 16%,transparent)}
/* Display + scope chips sit at the far right of the bar, away from the choosers on the left. */
.pf-lb-right{margin-left:auto}

/* Dropdown choosers (columns, each filter, saved views). Tick-to-exclude lists: everything starts on, so a
   department or status added later shows up by itself instead of being missing from every saved view. */
.pf-lb-dd{position:relative}
.pf-lb-ddbtn{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line-2);
  background:var(--surface);color:var(--text-2);border-radius:8px;padding:4px 10px;font-size:12.5px;
  cursor:pointer;font-family:inherit;transition:.12s}
.pf-lb-ddbtn:hover{border-color:var(--brand);color:var(--brand)}
.pf-lb-ddbtn.on{border-color:var(--brand);color:var(--brand);font-weight:600}
.pf-lb-ddbadge{font-size:11px;font-weight:700;color:var(--text-3);font-variant-numeric:tabular-nums}
.pf-lb-ddcaret{font-size:9px;color:var(--text-3)}
.pf-lb-ddbody{display:none;position:absolute;z-index:40;top:calc(100% + 5px);left:0;min-width:236px;
  max-width:320px;background:var(--surface);border:1px solid var(--line);border-radius:12px;
  box-shadow:0 18px 40px rgba(15,23,42,.16);padding:9px}
.pf-lb-dd.open .pf-lb-ddbody{display:block}
.pf-lb-search{width:100%;border:1px solid var(--line-2);border-radius:8px;background:var(--surface-2);
  color:var(--text);font:inherit;font-size:12.5px;padding:5px 9px;margin-bottom:7px}
.pf-lb-search:focus{outline:0;border-color:var(--brand)}
.pf-lb-checks{max-height:288px;overflow-y:auto;display:flex;flex-direction:column;gap:1px}
.pf-lb-check{display:flex;align-items:center;gap:8px;padding:4px 6px;border-radius:7px;font-size:13px;
  color:var(--text);cursor:pointer}
.pf-lb-check:hover{background:var(--surface-2)}
.pf-lb-check input{cursor:pointer}
.pf-lb-empty{font-size:12.5px;color:var(--text-3);padding:4px 6px}
.pf-lb-menufoot{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;padding-top:8px;
  border-top:1px solid var(--line)}
.pf-lb-action{border:1px solid var(--line-2);background:transparent;color:var(--text-2);border-radius:7px;
  padding:3px 9px;font-size:12px;cursor:pointer;font-family:inherit}
.pf-lb-action:hover{border-color:var(--brand);color:var(--brand)}
.pf-lb-viewrow{display:flex;align-items:center;gap:4px}
.pf-lb-viewname{flex:1;text-align:left;border:0;background:transparent;color:var(--text);font:inherit;
  font-size:13px;padding:4px 6px;border-radius:7px;cursor:pointer}
.pf-lb-viewname:hover{background:var(--surface-2)}
.pf-lb-viewname.on{color:var(--brand);font-weight:600}
.pf-lb-viewdel{border:0;background:transparent;color:var(--text-3);cursor:pointer;font-size:11px;
  padding:3px 6px;border-radius:6px;font-family:inherit}
.pf-lb-viewdel:hover{color:var(--danger);background:var(--surface-2)}

/* Sleeker density for list tables — tighter than the app-wide table default, with a fixed layout so the
   per-list column widths hold (and so a dragged width means exactly what it says). The table is as wide as
   its columns ask for and the wrapper scrolls, which is what lets "Fit page" scroll sideways rather than
   squeezing every cell into the page. */
.pf-wrap .pf-list .table{table-layout:fixed;font-size:13px}
.pf-wrap .pf-list .table>thead>tr>th{padding:8px 12px;overflow:hidden;text-overflow:ellipsis;
  position:relative;font-size:10.5px}
.pf-wrap .pf-list .table>tbody>tr>td{padding:7px 12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-wrap .pf-list .table td.num,.pf-wrap .pf-list .table th.num{font-variant-numeric:tabular-nums}
/* Compact: one more notch down for people who want the whole board on one screen. */
.pf-dense .pf-list .table{font-size:12.25px}
.pf-dense .pf-list .table>tbody>tr>td{padding:4px 10px}
.pf-dense .pf-list .table>thead>tr>th{padding:6px 10px}
.pf-dense .pf-list .badge{font-size:.66rem;padding:.28em .6em}

/* Sortable headers: click to sort, with the current direction marked. The grip is a resize handle on the
   column's trailing edge — drag to size, double-click to put it back to the table's default. */
.pf-wrap .pf-list .table>thead>tr>th.pf-th-sortable{cursor:pointer;user-select:none}
.pf-wrap .pf-list .table>thead>tr>th.pf-th-sortable:hover{color:var(--brand)}
.pf-wrap .pf-list .table>thead>tr>th.pf-th-asc::after{content:' ↑';color:var(--brand)}
.pf-wrap .pf-list .table>thead>tr>th.pf-th-desc::after{content:' ↓';color:var(--brand)}
.pf-th-grip{position:absolute;top:0;right:0;width:9px;height:100%;cursor:col-resize;touch-action:none}
.pf-th-grip:hover,body.pf-resizing .pf-th-grip{background:var(--brand);opacity:.55}
body.pf-resizing{cursor:col-resize;user-select:none}

/* Drag a header sideways to move that column, or a row in the Columns chooser for one that is switched off
   and so has no header to drag. The gap the column would land in is drawn on the neighbouring cell rather
   than the dragged one animated — a fixed-layout table would reflow on every frame. The order is part of the
   view state, so it is remembered and saved exactly like the widths and filters.
   touch-action is pan-y rather than none so a tablet can still scroll the page from the header row; phones
   never see any of this, because the card layout hides the thead outright. */
.pf-wrap .pf-list .table>thead>tr>th.pf-th-move{touch-action:pan-y}
.pf-wrap .pf-list .table>thead>tr>th.pf-th-dragging{background:var(--surface-2);opacity:.45}
body.pf-reordering{cursor:grabbing;user-select:none}
body.pf-reordering .pf-th-grip{background:none}
.pf-dropbefore{box-shadow:inset 2px 0 0 0 var(--brand)}
.pf-dropafter{box-shadow:inset -2px 0 0 0 var(--brand)}
/* The chooser reorders down the list, so its marks are horizontal. */
.pf-lb-cols .pf-dropbefore{box-shadow:inset 0 2px 0 0 var(--brand)}
.pf-lb-cols .pf-dropafter{box-shadow:inset 0 -2px 0 0 var(--brand)}
.pf-lb-grip{cursor:grab;color:var(--text-3);font-size:12px;line-height:1;touch-action:pan-y}
.pf-lb-check:hover .pf-lb-grip{color:var(--text)}
/* Reordering a list narrowed by the search box would move columns past gaps nobody can see. */
.pf-lb-cols.pf-nodrag .pf-lb-grip{visibility:hidden;cursor:default}

/* Full-bleed list pages. A fifteen-column table has no business inside the app's 1280px reading column, so a
   list page opts its shell out of the cap by stamping data-wide on #content — rendered that way server-side so
   there is no flash, and removed by pflist when the user picks "Fit page" (which then scrolls sideways instead).
   Scoped with :has() so every other page keeps the standard measure; browsers without :has() stay capped. */
main.pf-wrap:has(#content[data-wide]){max-width:none}
/* Flag column (jobs list): row-state badges/icons live here, right-aligned, and wrap onto a second line
   rather than being ellipsised — the truncation the other cells want would hide a flag entirely. */
.pf-wrap .pf-list .table td.pf-flagcell{white-space:normal;text-overflow:clip}
.pf-flags{display:flex;flex-wrap:wrap;gap:4px;align-items:center;justify-content:flex-end}

/* ============ LIST ROWS AS CARDS ON A PHONE (≤760px) ============
   A seventeen-column table cannot be read on a phone, and dragging it sideways to find a due date is not
   reading it either. Below 760px each pflist row leaves table layout and becomes a small card.

   Cells are placed by the `data-card-slot` pflist stamps from the list's `data-card` map, never by column
   name — that is what keeps this one block able to serve jobs, quotes, orders and invoices, whose column
   keys have nothing in common. A list without `data-card` stamps no slots, matches nothing here, and keeps
   the scrolling table it has today. Adding a list is one attribute in its template.

   The card's contents are deliberately a fixed set rather than the user's chosen desktop columns: there is
   room for what names the row and what makes it urgent, and nothing else. Filtering and sorting are
   untouched — pflist drives those from inline row display and tbody order, both of which still apply. */
@media(max-width:760px){
  /* Gated on .pf-cards, which pflist.js adds only to a list whose template declares data-card.
     Without the gate these rules hid every cell of every list on a phone and re-showed only the ones
     carrying a card slot — so a list that never opted in (quotes, orders, invoices, payments) drew a
     column of empty rows. The gate also means a JS failure leaves the scrolling table rather than a
     blank one. */
  .pf-wrap .pf-list.pf-cards .table,
  .pf-wrap .pf-list.pf-cards .table>tbody{display:block}
  /* pflist writes the summed pixel width of the visible columns onto the element itself, so only
     !important gets the table back down to the width of the phone. */
  .pf-wrap .pf-list.pf-cards .table{width:auto!important;min-width:0!important;table-layout:auto;font-size:14px}
  .pf-wrap .pf-list.pf-cards .table>thead{display:none}
  .pf-wrap .pf-list.pf-cards .table-responsive{overflow-x:visible}

  .pf-wrap .pf-list.pf-cards .table>tbody>tr[data-row]{
    display:grid;position:relative;
    grid-template-columns:minmax(0,1fr) auto auto;
    grid-template-areas:
      "num    status status"
      "title  title  title"
      "sub    sub    sub"
      "meta   due    aux"
      "flags  flags  flags";
    align-items:start;column-gap:10px;
    padding:12px 14px;border-bottom:1px solid var(--line);
  }
  /* Every card keeps its hairline, including the last — the alternative needs to know which row is last
     *visible*, and pflist reorders and hides rows as you filter, so no static selector can say. The final
     hairline sits on the card's own bottom edge, where it reads as the container's border anyway. */

  /* Anything without a slot is off the card. */
  .pf-wrap .pf-list.pf-cards .table>tbody>tr>td{display:none;padding:0;border:0;overflow:visible;white-space:normal}
  .pf-wrap .pf-list.pf-cards .table>tbody>tr>td[data-card-slot]{display:block}
  .pf-wrap .pf-list.pf-cards td[data-card-slot="num"]{grid-area:num;font-weight:700;white-space:nowrap}
  .pf-wrap .pf-list.pf-cards td[data-card-slot="status"]{grid-area:status;justify-self:end}
  .pf-wrap .pf-list.pf-cards td[data-card-slot="title"]{grid-area:title;margin-top:3px;font-weight:600;color:var(--text)}
  .pf-wrap .pf-list.pf-cards td[data-card-slot="sub"]{grid-area:sub;margin-top:1px;font-size:13px;color:var(--text-2)}
  .pf-wrap .pf-list.pf-cards td[data-card-slot="meta"]{grid-area:meta;margin-top:5px;font-size:12.5px;color:var(--text-3)}
  .pf-wrap .pf-list.pf-cards td[data-card-slot="due"]{grid-area:due;margin-top:5px;font-size:12.5px;
    color:var(--text-2);white-space:nowrap}
  .pf-wrap .pf-list.pf-cards td[data-card-slot="aux"]{grid-area:aux;margin-top:5px;font-size:12.5px;
    white-space:nowrap;text-align:right}
  .pf-wrap .pf-list.pf-cards td[data-card-slot="flags"]{grid-area:flags}
  .pf-wrap .pf-list.pf-cards td[data-card-slot="flags"] .pf-flags{justify-content:flex-start;margin-top:7px}
  /* A row carrying no flags must not leave the gap where its badges would have been. */
  .pf-wrap .pf-list.pf-cards td[data-card-slot="flags"]:has(.pf-flags:empty){display:none}

  /* The whole card opens the record. On a phone the job number alone is a small target, and it is the
     only link on the card once the other columns drop out, so nothing is shadowed by the overlay. */
  .pf-wrap .pf-list.pf-cards td[data-card-slot="num"] a::after{content:"";position:absolute;inset:0}

  /* The empty-state rows span the table and are prose, not a card. */
  .pf-wrap .pf-list.pf-cards .table>tbody>tr:not([data-row]){display:block}
  .pf-wrap .pf-list.pf-cards .table>tbody>tr:not([data-row])>td{display:block;padding:18px 14px;text-align:center}

  /* Card view has no columns to choose, no rows to tighten and no width to fit. The filters, the sorts,
     the saved views and the closed-scope chip all still mean something, so they stay. */
  .pf-listbar .pf-lb-dd:has(.pf-lb-menu-cols),
  .pf-listbar .pf-lb-density,
  .pf-listbar .pf-lb-width{display:none}
}

/* ============ OPERATOR (shop-floor station — touch-scaled, never scrolls at 1920×1080) ============
   Rebuilt 2026-09-24 from concept/operator-concept.html. Top to bottom: the scan bar (with the station picker),
   where the job is up to (the job page's river, compact, over the job's own status steps), the four print
   tiles, then Clear · the job · Advance. With nothing scanned, the stage row is "Ready to scan" beside the
   station's "Up next" list. */
/* The station is a fixed screen that must never scroll, so the page's usual bottom breathing room is dead
   space here. Scoped with :has() so no other page is affected. */
main.pf-wrap:has(.op-screen){padding-top:20px;padding-bottom:16px}

/* head: title, scan bar and station on one line */
.pf-wrap .op-head{display:flex;align-items:center;gap:16px;margin-bottom:14px}
.pf-wrap .op-head h1{flex:none}
.pf-wrap .op-scanbar{flex:1;min-width:0;display:flex;align-items:center;gap:14px;background:var(--surface);
  border:2px solid var(--brand);border-radius:14px;box-shadow:var(--shadow-sm);padding:9px 16px}
.pf-wrap .op-scanicon{color:var(--brand);display:flex;flex:none}
.pf-wrap .op-scanicon svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}
.pf-wrap .op-scaninput{flex:1;min-width:0;border:0;background:transparent;color:var(--text);font-size:20px;font-weight:700;padding:0;outline:0}
.pf-wrap .op-scaninput::placeholder{color:var(--text-3);font-weight:600}
.pf-wrap .op-scanhint{color:var(--text-3);font-size:12px;white-space:nowrap;flex:none}
.pf-wrap .op-stationbtn{flex:none;display:flex;align-items:center;gap:8px;background:var(--surface);border:1px solid var(--line-2);
  border-radius:12px;padding:9px 13px;font-family:inherit;font-size:13px;font-weight:600;color:var(--text-2);cursor:pointer;
  max-width:340px;min-height:44px}
.pf-wrap .op-stationbtn:hover{border-color:var(--brand)}
.pf-wrap .op-stationbtn b{color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pf-wrap .op-stationbtn .dot{width:8px;height:8px;border-radius:50%;background:var(--ok);flex:none}
.pf-wrap .op-stationbtn.unset .dot{background:var(--text-3)}

/* A message floats over the page rather than pushing it down: in the flow it cost the screen its no-scroll fit. */
.pf-wrap .op-screen{position:relative}
.pf-wrap .op-msg{position:absolute;left:50%;transform:translateX(-50%);top:62px;z-index:30;max-width:min(720px,90%);
  border-radius:12px;padding:11px 18px;font-size:14.5px;font-weight:600;box-shadow:var(--shadow);border:1px solid currentColor}
.pf-wrap .op-msg.ok{background:color-mix(in srgb,var(--ok) 12%,var(--surface));color:var(--ok)}
.pf-wrap .op-msg.err{background:color-mix(in srgb,var(--danger) 10%,var(--surface));color:var(--danger)}

/* where it's up to: the job page's river (the .pf-flow parts, shorter), and the status steps under it */
.pf-wrap .op-flow{padding:12px 18px 10px;margin-bottom:12px;border-radius:20px}
.pf-wrap .op-flow .pf-cap{padding:0 6px 2px}
.pf-wrap .op-flow .pf-cap h2{font-size:12px}
.pf-wrap .op-flow .pf-swoosh{width:280px;top:-40px}
.pf-wrap .op-stations{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:4px}
.pf-wrap .op-station{display:flex;flex-direction:column;align-items:center;min-width:0;text-align:center;padding:4px 4px 0}
.pf-wrap .op-station .l{font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3)}
.pf-wrap .op-station .c{font-size:15px;font-weight:600;letter-spacing:-.01em;line-height:1.25;color:var(--text-3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.pf-wrap .op-station .s{font-size:11.5px;font-weight:600;color:var(--text-3);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;max-width:100%;min-height:1.5em}
.pf-wrap .op-station.done .l,.pf-wrap .op-station.done .s{color:var(--text-2)}
.pf-wrap .op-station.done .c{color:var(--text)}
.pf-wrap .op-station.now .l,.pf-wrap .op-station.now .c{color:var(--brand)}
.pf-wrap .op-station.now .s{color:var(--text-2)}
.pf-wrap .op-station.skip .c{font-weight:400}
.pf-wrap .op-river{display:block;width:100%;height:auto;margin-top:-2px}
.pf-wrap .op-river .op-dot{fill:var(--surface);stroke:var(--line-2);stroke-width:2.6}
.pf-wrap .op-river .op-halo{fill:var(--brand);opacity:0}
.pf-wrap .op-river .done .op-dot{fill:url(#op-flowgrad);stroke:url(#op-flowgrad)}
.pf-wrap .op-river .now .op-dot{stroke:url(#op-flowgrad)}
.pf-wrap .op-river .now .op-halo{opacity:.16;animation:op-halo 2.4s ease-in-out infinite;transform-origin:center;transform-box:fill-box}
.pf-wrap .op-river .skip .op-dot{stroke-dasharray:3 3}
.pf-wrap .op-river .pf-flow-line{stroke:url(#op-flowgrad)}
@keyframes op-halo{50%{transform:scale(1.4);opacity:.05}}
@media (prefers-reduced-motion:reduce){.pf-wrap .op-river .now .op-halo,.pf-wrap .op-river .pf-flow-dots{animation:none}}

/* The shop-floor zoom: the river says "Production", the steps say "Production now, Finishing next". Tapping a
   step sets it, after a confirm — this replaced the old Set status dropdown. */
.pf-wrap .op-steps{display:flex;align-items:stretch;border-top:1px solid var(--line);margin-top:4px;padding-top:8px}
.pf-wrap .op-stepgroup{display:flex;flex-direction:column;gap:5px;min-width:0}
.pf-wrap .op-stepgroup+.op-stepgroup{margin-left:10px;padding-left:10px;border-left:1px dashed var(--line-2)}
.pf-wrap .op-stepgroup .gl{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text-3);padding-left:2px}
.pf-wrap .op-steprow{display:flex;gap:4px;flex-wrap:nowrap}
.pf-wrap .op-step{position:relative;display:flex;align-items:center;gap:6px;border:1px solid var(--line-2);background:var(--surface);
  border-radius:999px;padding:5px 10px 5px 7px;font-family:inherit;font-size:12.5px;font-weight:600;color:var(--text-3);
  white-space:nowrap;cursor:pointer;transition:.13s;min-height:36px}
.pf-wrap .op-step:hover{border-color:var(--brand)}
.pf-wrap .op-step i{width:16px;height:16px;border-radius:50%;border:2px solid var(--line-2);flex:none;display:grid;
  place-items:center;font-style:normal;font-size:10px;line-height:1;color:#fff}
.pf-wrap .op-step.done{color:var(--text-2)}
.pf-wrap .op-step.done i{background:var(--ok);border-color:var(--ok)}
.pf-wrap .op-step.done i::after{content:"\2713";font-weight:800}
.pf-wrap .op-step.now{background:var(--brand);border-color:var(--brand);color:#fff;box-shadow:0 0 0 4px var(--cyan-soft);cursor:default}
.pf-wrap .op-step.now i{border-color:#fff;background:#fff;box-shadow:inset 0 0 0 3px var(--brand)}
.pf-wrap .op-step.next{border-color:var(--ok);color:var(--ok);border-style:dashed}
.pf-wrap .op-step.next::before{content:"next";position:absolute;top:-8px;right:8px;font-size:9px;font-weight:800;letter-spacing:.06em;
  text-transform:uppercase;background:var(--ok);color:#fff;border-radius:6px;padding:0 5px;line-height:14px}
.pf-wrap .op-steps.locked .op-step{cursor:not-allowed;opacity:.55}
.pf-wrap .op-steps.locked .op-step.now{opacity:1;background:var(--text-3);border-color:var(--text-3);box-shadow:none}
.pf-wrap .op-elapsed{margin-left:auto;align-self:center;text-align:right;font-size:12px;color:var(--text-3);padding-left:12px;max-width:150px}
.pf-wrap .op-elapsed b{display:block;color:var(--text);font-size:15px;font-weight:700}

/* the four print tiles, one line each; every one always shown, the illegal ones disabled with the reason */
.pf-wrap .op-toptiles{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:12px}
.pf-wrap .op-tile{display:flex;align-items:center;gap:11px;text-align:left;text-decoration:none;background:var(--surface);
  border:1px solid var(--line-2);border-radius:14px;padding:10px 14px;cursor:pointer;transition:.13s;color:var(--text);
  font-family:inherit;min-width:0}
.pf-wrap .op-tile:hover{border-color:var(--brand);box-shadow:var(--shadow-sm)}
.pf-wrap .op-tile>div{min-width:0}
.pf-wrap .op-tileic{width:34px;height:34px;border-radius:10px;background:var(--cyan-soft);color:var(--brand);display:grid;place-items:center;flex:none}
.pf-wrap .op-tileic svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.pf-wrap .op-tiletitle{font-weight:700;font-size:14.5px;line-height:1.2}
.pf-wrap .op-tiledesc{color:var(--text-2);font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pf-wrap .op-tile-off{cursor:not-allowed;opacity:.6;background:var(--chip)}
.pf-wrap .op-tile-off:hover{border-color:var(--line-2);box-shadow:none}
.pf-wrap .op-soonbadge{font-size:9.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:var(--text-3);background:var(--chip);border-radius:6px;padding:1px 5px;margin-left:4px;vertical-align:1px}

/* stage row: [clear] · [job card] · [advance|complete]. Side tiles are a fixed width so the job gets the room.
   Columns are placed explicitly, so a hidden side tile leaves its track standing and the card stays centred. */
.pf-wrap .op-stage{display:grid;grid-template-columns:190px minmax(0,1fr) 190px;gap:12px;align-items:stretch}
.pf-wrap .op-stage>.op-side:first-child{grid-column:1}
.pf-wrap .op-center{grid-column:2;min-width:0}
.pf-wrap .op-stage>.op-side:last-child{grid-column:3}
.pf-wrap .op-center>*{height:100%}
.pf-wrap .op-stage.is-empty{grid-template-columns:minmax(0,1fr)}
.pf-wrap .op-stage.is-empty .op-center{grid-column:1}

/* nothing scanned: ready, beside the station's queue */
.pf-wrap .op-emptycard{background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow);
  box-sizing:border-box;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.5fr);min-height:420px;overflow:hidden}
.pf-wrap .op-ready{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:30px;
  border-right:1px solid var(--line);background:var(--hero-bg)}
.pf-wrap .op-emptyicon svg{width:52px;height:52px;fill:none;stroke:var(--text-3);stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round}
.pf-wrap .op-emptytitle{font-size:20px;font-weight:600;color:var(--text);margin-top:10px}
.pf-wrap .op-emptytext{color:var(--text-3);font-size:13.5px;margin-top:4px;max-width:300px}
.pf-wrap .op-queue{padding:16px 20px;min-width:0}
.pf-wrap .op-queuehead{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:8px}
.pf-wrap .op-queuehead h2{margin:0;font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pf-wrap .op-queuehead span{font-size:12.5px;color:var(--text-3);white-space:nowrap}
.pf-wrap .op-q{display:grid;grid-template-columns:96px minmax(0,1fr) auto 104px;gap:12px;align-items:center;width:100%;
  padding:9px 12px;border-radius:12px;border:1px solid var(--line);background:var(--surface);margin-bottom:6px;
  cursor:pointer;transition:.13s;text-align:left;font-family:inherit;color:var(--text)}
.pf-wrap .op-q:hover{border-color:var(--brand);background:var(--cyan-soft)}
.pf-wrap .op-qn{font-weight:700;color:var(--brand);font-variant-numeric:tabular-nums}
.pf-wrap .op-qw{min-width:0}
.pf-wrap .op-qw b{display:block;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pf-wrap .op-qw b .op-badge{margin-left:6px;vertical-align:2px}
.pf-wrap .op-qw>span{display:block;font-size:12px;color:var(--text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pf-wrap .op-qd{font-size:12.5px;font-weight:700;text-align:right;white-space:nowrap}
.pf-wrap .op-qd.soon{color:var(--warn)}
.pf-wrap .op-qd.late{color:var(--danger)}
.pf-wrap .op-queuenote{color:var(--text-3);font-size:13px;padding:14px 2px}

/* the job card */
.pf-wrap .op-jobcard{background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow);
  box-sizing:border-box;display:flex;flex-direction:column;min-width:0}
.pf-wrap .op-jhead{display:flex;justify-content:space-between;align-items:flex-start;gap:14px;padding:14px 20px 10px;border-bottom:1px solid var(--line)}
.pf-wrap .op-jn{font-size:26px;font-weight:700;color:var(--brand);letter-spacing:-.01em;line-height:1.1;font-variant-numeric:tabular-nums;
  text-decoration:none}
.pf-wrap .op-jn:hover{text-decoration:underline}
.pf-wrap .op-title{font-size:17px;font-weight:600;margin-top:4px;color:var(--text)}
.pf-wrap .op-order{color:var(--text-2);font-size:13.5px;margin-top:1px}
.pf-wrap .op-badges{display:inline-flex;gap:5px;flex-wrap:wrap;vertical-align:4px;margin-left:8px}
.pf-wrap .op-badge{display:inline-flex;align-items:center;font-size:10.5px;font-weight:700;border-radius:999px;padding:2px 9px;
  text-transform:uppercase;letter-spacing:.03em;background:var(--chip);color:var(--text-2)}
.pf-wrap .op-badge.rush{background:var(--danger-soft);color:var(--danger)}
.pf-wrap .op-badge.reprint{background:var(--warn-soft);color:var(--warn)}
.pf-wrap .op-badge.labels{background:var(--cyan-soft);color:var(--brand)}
.pf-wrap .op-badge.approved,.pf-wrap .op-badge.garments{background:var(--ok-soft);color:var(--ok)}
.pf-wrap .op-badge.garments svg{width:12px;height:12px;margin-right:4px;fill:none;stroke:currentColor;stroke-width:2.2}
/* Due: the one date the floor lives by, coloured by how close it is. */
.pf-wrap .op-due{flex:none;text-align:right;border-radius:14px;padding:8px 14px;background:var(--surface-2);min-width:150px;max-width:300px}
.pf-wrap .op-due .k{font-size:10.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--text-3)}
.pf-wrap .op-due .v{font-size:21px;font-weight:700;letter-spacing:-.02em;line-height:1.2;color:var(--text)}
.pf-wrap .op-due .s{font-size:12px;font-weight:600;color:var(--text-2)}
.pf-wrap .op-due.soon{background:var(--warn-soft)}
.pf-wrap .op-due.soon .v,.pf-wrap .op-due.soon .s{color:var(--warn)}
.pf-wrap .op-due.late{background:var(--danger-soft)}
.pf-wrap .op-due.late .v,.pf-wrap .op-due.late .s{color:var(--danger)}

/* Held, or garments not all in: the reason has to be readable across the shop. The hold is hit here, so it
   can be lifted here — the button sits beside the reason, sized for a gloved tap. */
.pf-wrap .op-alerts{padding:12px 20px 0;display:flex;flex-direction:column;gap:8px}
.pf-wrap .op-alert{border-radius:12px;padding:8px 12px;border:1px solid;display:grid;grid-template-columns:minmax(0,1fr) auto;
  column-gap:14px;align-items:center}
.pf-wrap .op-alert>*{grid-column:1}
.pf-wrap .op-alert>.op-holdlift{grid-column:2;grid-row:1/span 3}
.pf-wrap .op-alerttitle{display:flex;align-items:center;gap:7px;font-weight:800;font-size:12.5px;letter-spacing:.06em;text-transform:uppercase}
.pf-wrap .op-alerttitle svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;flex:none}
.pf-wrap .op-alerttext{color:var(--text);font-size:15px;font-weight:600;margin-top:3px}
.pf-wrap .op-alerthint{color:var(--text-3);font-size:12px;margin-top:2px}
.pf-wrap .op-alert.hold{border-color:var(--warn);background:var(--warn-soft)}
.pf-wrap .op-alert.hold .op-alerttitle{color:var(--warn)}
.pf-wrap .op-alert.garm{border-color:var(--danger);background:var(--danger-soft)}
.pf-wrap .op-alert.garm .op-alerttitle{color:var(--danger)}
.pf-wrap .op-holdlift{border:2px solid var(--warn);background:transparent;color:var(--warn);border-radius:10px;padding:10px 16px;
  font-family:inherit;font-size:13.5px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;transition:.13s}
.pf-wrap .op-holdlift:hover{background:var(--warn);color:#fff}

.pf-wrap .op-jbody{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);gap:18px;padding:12px 20px;flex:1}
.pf-wrap .op-h{font-size:10.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text-3);margin:0 0 6px}
.pf-wrap .op-qty{display:flex;align-items:baseline;gap:10px;margin-bottom:4px;min-width:0}
.pf-wrap .op-qty b{font-size:30px;font-weight:300;letter-spacing:-.03em;line-height:1;font-variant-numeric:tabular-nums}
.pf-wrap .op-qty span{font-size:15px;font-weight:600;color:var(--text-2)}
.pf-wrap .op-facts{margin-top:8px}
.pf-wrap .op-fact{display:grid;grid-template-columns:92px minmax(0,1fr);gap:10px;padding:5px 0;border-top:1px solid var(--line);font-size:13.5px}
.pf-wrap .op-fact .k{color:var(--text-2)}
.pf-wrap .op-fact .v{font-weight:600;min-width:0;white-space:pre-line}
/* The detail rides on the same line (a row each would cost the station its no-scroll fit at 1080). */
.pf-wrap .op-fact .v small{font-weight:500;color:var(--text-2);font-size:12px;margin-left:8px}
.pf-wrap .op-pill{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:600;border-radius:999px;padding:1px 8px;
  margin-left:6px;background:var(--chip);color:var(--text-2);vertical-align:1px;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}
.pf-wrap .op-pill::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.pf-wrap .op-pill.ok{background:var(--ok-soft);color:var(--ok)}
.pf-wrap .op-pill.warn{background:var(--warn-soft);color:var(--warn)}
/* apparel sizes, transposed like the line's size grid */
.pf-wrap .op-sizes{margin-top:8px}
.pf-wrap .op-sizesname{font-size:12px;font-weight:600;color:var(--text-2);margin-bottom:3px}
.pf-wrap .op-sizegrid{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);border:1px solid var(--line);border-radius:10px;
  overflow:hidden;text-align:center;margin-bottom:6px}
.pf-wrap .op-sizegrid>div{border-left:1px solid var(--line);padding:4px 2px}
.pf-wrap .op-sizegrid>div:first-child{border-left:0}
.pf-wrap .op-sizegrid .sz{font-size:11px;font-weight:700;color:var(--text-3)}
.pf-wrap .op-sizegrid .sq{font-size:15px;font-weight:700}
.pf-wrap .op-sizegrid .sr{font-size:10.5px;font-weight:700;color:var(--ok)}
.pf-wrap .op-sizegrid .sr.short{color:var(--danger)}
/* completed so far, with the same log the job page keeps */
.pf-wrap .op-prog{margin-top:10px;background:var(--surface-2);border-radius:12px;padding:9px 12px}
.pf-wrap .op-progrow{display:flex;justify-content:space-between;align-items:center;gap:10px;font-size:13px}
.pf-wrap .op-progrow b{font-size:15px}
.pf-wrap .op-bar{height:7px;border-radius:7px;background:var(--chip);overflow:hidden;margin-top:7px}
.pf-wrap .op-bar>i{display:block;height:100%;background:linear-gradient(90deg,var(--brand),var(--brand-2));border-radius:7px}
.pf-wrap .op-bar>i.full{background:var(--ok)}
.pf-wrap .op-btn{border:1px solid var(--line-2);background:var(--surface);border-radius:10px;padding:7px 12px;font-family:inherit;
  font-weight:600;font-size:12.5px;cursor:pointer;white-space:nowrap;color:var(--text);text-decoration:none;display:inline-block}
.pf-wrap .op-btn:hover{border-color:var(--brand);color:var(--brand)}
/* right column: what it should look like, what to watch for, what goes with it */
.pf-wrap .op-art{display:flex;gap:12px;align-items:stretch;border:1px solid var(--line);border-radius:14px;padding:8px 10px;margin-bottom:10px}
.pf-wrap .op-thumb{width:64px;height:88px;border-radius:6px;flex:none;box-shadow:0 2px 8px rgba(6,38,55,.18);overflow:hidden;
  cursor:zoom-in;padding:0;border:0;background:var(--surface-2);display:grid;place-items:center}
.pf-wrap .op-thumb img{width:100%;height:100%;object-fit:contain;background:#fff;display:block}
.pf-wrap .op-thumb.none{cursor:default;box-shadow:none;border:1px dashed var(--line-2);color:var(--text-3);font-size:11px;padding:6px}
.pf-wrap .op-artmeta{display:flex;flex-direction:column;gap:3px;min-width:0;font-size:12.5px;color:var(--text-2)}
.pf-wrap .op-artmeta b{color:var(--text);font-size:13.5px}
.pf-wrap .op-artmeta .op-btn{margin-top:auto;align-self:flex-start}
.pf-wrap .op-notes{background:var(--warn-soft);border-radius:12px;padding:9px 12px;font-size:13.5px;margin-bottom:10px;white-space:pre-line}
.pf-wrap .op-notes b{display:block;font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--warn);margin-bottom:2px}
.pf-wrap .op-sibs{border:1px solid var(--line);border-radius:12px;overflow:hidden}
.pf-wrap .op-sibshead{font-size:12px;font-weight:600;color:var(--text-2);padding:6px 11px;background:var(--surface-2)}
.pf-wrap .op-sib{display:flex;align-items:center;gap:8px;padding:6px 11px;border-top:1px solid var(--line);font-size:13px}
.pf-wrap .op-sib .sn{font-weight:700;flex:none;font-variant-numeric:tabular-nums}
.pf-wrap .op-sib .sw{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--text-2)}
.pf-wrap .op-sib .ss{font-size:11.5px;font-weight:700;border-radius:999px;padding:1px 8px;background:var(--chip);color:var(--text-2);white-space:nowrap}
.pf-wrap .op-sib .ss.ok{background:var(--ok-soft);color:var(--ok)}
.pf-wrap .op-sib .ss.warn{background:var(--warn-soft);color:var(--warn)}
.pf-wrap .op-sib.me{background:var(--cyan-soft)}
.pf-wrap .op-sib.me .sn{color:var(--brand)}
.pf-wrap .op-order{display:flex;align-items:center;gap:6px 14px;flex-wrap:wrap}
.pf-wrap .op-people{display:inline-flex;align-items:center;gap:12px;flex-wrap:wrap;font-size:12.5px;color:var(--text-2)}
.pf-wrap .op-who{display:flex;align-items:center;gap:6px}
.pf-wrap .op-av{width:22px;height:22px;border-radius:50%;display:grid;place-items:center;font-size:9.5px;font-weight:700;color:#fff;background:#6f5bd6;flex:none}
.pf-wrap .op-av.rep{background:#0b8f6a}

/* flanking scan tiles — a big QR you scan (or tap) to act */
.pf-wrap .op-side{min-width:0;display:flex}
.pf-wrap .op-sidetile{width:100%;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;
  justify-content:space-between;gap:10px;text-align:center;border-radius:18px;padding:16px 12px;cursor:pointer;font-family:inherit;
  transition:.13s;border:2px solid var(--line-2);background:var(--surface);color:var(--text-2)}
.pf-wrap .op-sidetitle{font-size:18px;font-weight:800;text-transform:uppercase;letter-spacing:.02em;line-height:1.15}
.pf-wrap .op-sidesub{display:block;font-size:14px;font-weight:600;text-transform:none;letter-spacing:0;margin-top:3px}
.pf-wrap .op-sidehint{font-size:11.5px;color:var(--text-3)}
.pf-wrap .op-qrbox{background:#fff;border-radius:10px;padding:8px;width:100%;display:flex;justify-content:center}
.pf-wrap .op-qrbig{width:clamp(96px,14vh,130px);height:clamp(96px,14vh,130px);display:block}
.pf-wrap .op-sidetile-advance,.pf-wrap .op-sidetile-complete{border-color:var(--ok);color:var(--ok)}
.pf-wrap .op-sidetile-advance:hover,.pf-wrap .op-sidetile-complete:hover{background:var(--ok-soft)}
.pf-wrap .op-sidetile-clear:hover{border-color:var(--danger);color:var(--danger);background:var(--danger-soft)}
.pf-wrap .op-sidetile-locked{border-style:dashed;cursor:not-allowed;color:var(--text-3)}

/* dialogs: lift hold, status step confirm, log completed, station — shop-floor sized */
.pf-wrap .op-dlg{border-radius:18px}
.pf-wrap .op-dlg .modal-title{font-weight:700}
.pf-wrap .op-dlglabel{font-size:12px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3)}
.pf-wrap .op-liftreason{font-size:20px;font-weight:700;color:var(--text);margin-top:6px}
.pf-wrap .op-liftby{font-size:13px;color:var(--text-3);margin-top:6px}
.pf-wrap .op-dlgactions{display:flex;flex-wrap:wrap;gap:10px}
.pf-wrap .op-dlgbtn{flex:1 1 auto;min-width:132px;border-radius:12px;padding:13px 16px;font-family:inherit;
  font-size:14px;font-weight:800;text-transform:uppercase;letter-spacing:.03em;cursor:pointer;
  transition:.13s;border:2px solid var(--line-2);background:var(--surface);color:var(--text-2)}
.pf-wrap .op-dlgbtn:hover{border-color:var(--text-3)}
.pf-wrap .op-dlgbtn.go{border-color:var(--brand);background:var(--brand);color:#fff}
.pf-wrap .op-dlgbtn.go:hover{background:var(--brand-deep);border-color:var(--brand-deep)}
.pf-wrap .op-liftgo{border-color:var(--warn);color:var(--warn)}
.pf-wrap .op-liftgo:hover{background:var(--warn);color:#fff}
.pf-wrap .op-liftsla{border-color:var(--brand);color:var(--brand)}
.pf-wrap .op-liftsla:hover{background:var(--brand);color:#fff}
.pf-wrap .op-liftslasub{display:block;font-size:12px;font-weight:600;text-transform:none;letter-spacing:0;margin-top:3px;opacity:.8}
.pf-wrap .op-move{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-weight:700;font-size:16px;margin-top:10px}
.pf-wrap .op-move span{border-radius:999px;padding:6px 14px;background:var(--chip);color:var(--text-2)}
.pf-wrap .op-move span.to{background:var(--brand);color:#fff}
.pf-wrap .op-dlg .form-control,.pf-wrap .op-dlg .form-select{min-height:48px;font-size:17px;font-weight:600;border-radius:12px}
.pf-wrap .op-dlghint{font-size:12.5px;color:var(--text-3);margin-top:6px}
/* the approved proof, full screen */
.pf-wrap .op-lightbox .modal-content{background:transparent;border:0;box-shadow:none;align-items:center;gap:10px}
.pf-wrap .op-lightbox img{max-width:100%;max-height:82vh;border-radius:8px;box-shadow:0 20px 60px rgba(0,0,0,.45);background:#fff;cursor:zoom-out}
.pf-wrap .op-lightboxcap{color:#fff;font-weight:600;text-align:center;text-shadow:0 1px 3px rgba(0,0,0,.5)}
.pf-wrap .op-lightboxcap small{display:block;font-weight:500;opacity:.8}

/* narrower screens: the station is 1920×1080, but a laptop or tablet should still work */
@media(max-width:1100px){
  .pf-wrap .op-stage{grid-template-columns:150px minmax(0,1fr) 150px}
  .pf-wrap .op-jbody{grid-template-columns:minmax(0,1fr)}
}
@media(max-width:900px){
  .pf-wrap .op-head{flex-wrap:wrap}
  .pf-wrap .op-scanbar{order:3;flex-basis:100%}
  .pf-wrap .op-scanhint{display:none}
  .pf-wrap .op-toptiles{grid-template-columns:repeat(2,minmax(0,1fr))}
  .pf-wrap .op-stage{grid-template-columns:minmax(0,1fr)}
  /* Drop the explicit column placement, or every tile would pile into a single track. */
  .pf-wrap .op-stage>.op-side:first-child,.pf-wrap .op-center,
  .pf-wrap .op-stage>.op-side:last-child{grid-column:auto}
  .pf-wrap .op-center{order:0}
  .pf-wrap #op-right{order:1}
  .pf-wrap #op-left{order:2}
  .pf-wrap .op-stations{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
  .pf-wrap .op-river{display:none}
  .pf-wrap .op-steps{flex-direction:column;gap:10px}
  .pf-wrap .op-steprow{flex-wrap:wrap}
  .pf-wrap .op-stepgroup+.op-stepgroup{margin-left:0;padding-left:0;border-left:0}
  .pf-wrap .op-elapsed{margin-left:0;text-align:left;padding-left:0}
  .pf-wrap .op-emptycard{grid-template-columns:minmax(0,1fr)}
  .pf-wrap .op-ready{border-right:0;border-bottom:1px solid var(--line)}
}
@media(max-width:560px){
  .pf-wrap .op-toptiles{grid-template-columns:minmax(0,1fr)}
  .pf-wrap .op-jhead{flex-direction:column}
  .pf-wrap .op-due{text-align:left;width:100%;max-width:none}
  .pf-wrap .op-stations{grid-template-columns:repeat(2,minmax(0,1fr))}
  .pf-wrap .op-q{grid-template-columns:minmax(0,1fr) auto}
  .pf-wrap .op-q .op-qn{grid-column:1/-1}
  .pf-wrap .op-q .op-badge{display:none}
}

/* ============ KIOSK LAYOUT RESET ============
   layout/bare.html loads this stylesheet on its own — no Bootstrap, so no Reboot. The two resets
   every other page inherits from Bootstrap have to be stated here, and a viewport-height display
   genuinely needs both: without border-box the 22px padding below is *added* to height:100vh, and
   the default body margin adds another 16px, so a "100vh" wall board ends up taller than the screen
   and quietly pushes its last row out of sight. */
.pf-kiosk{margin:0}
.pf-kiosk,.pf-kiosk *,.pf-kiosk *::before,.pf-kiosk *::after{box-sizing:border-box}

/* ============ FREIGHT TV DISPLAY (kiosk: chrome-less, read-only, large) ============ */
.pf-tv{padding:22px 26px;min-height:100vh}
/* Header: home + logo + board name on the left, wall clock on the right (fragments/tv-head.html). */
.pf-tv-head{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:16px}
.pf-tv-ident{display:flex;align-items:center;gap:16px;min-width:0}
.pf-tv-title{font-size:32px;font-weight:700;letter-spacing:-.02em;color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-tv-updated{font-size:15px;color:var(--text-3);white-space:nowrap}

/* The only control on the screen, so it is finger-sized: these get tapped on a wall-mounted panel. */
.pf-tv-home{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:46px;height:46px;border-radius:13px;background:var(--surface-2);border:1px solid var(--line);
  color:var(--text-2);text-decoration:none;transition:background .15s ease,color .15s ease}
.pf-tv-home:hover,.pf-tv-home:focus-visible{background:var(--chip);color:var(--brand)}
.pf-tv-home svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
.pf-tv-brand{display:flex;align-items:center;flex:0 0 auto}
.pf-tv-brand .pf-logo{height:30px;width:auto;display:block}
.pf-tv-brand .lw{fill:var(--logo-ink)}
.pf-tv-brand .lm{fill:var(--brand)}
.pf-tv-sep{flex:0 0 auto;width:1px;height:32px;background:var(--line-2)}

.pf-tv-right{display:flex;align-items:center;gap:18px;flex:0 0 auto}
.pf-tv-now{flex:0 0 auto;text-align:right}
.pf-tv-clock{font-size:40px;font-weight:700;line-height:1.05;letter-spacing:-.02em;color:var(--text);
  font-variant-numeric:tabular-nums}

/* Department filter. The one setting on an otherwise read-only board, so the hit targets are sized for
   a finger on a wall-mounted panel rather than a mouse. */
.pf-tv-filter{position:relative;flex:0 0 auto}
.pf-tv-filterbtn{display:inline-flex;align-items:center;gap:9px;height:46px;padding:0 16px;
  border-radius:13px;background:var(--surface-2);border:1px solid var(--line);color:var(--text-2);
  font-family:inherit;font-size:15px;font-weight:600;cursor:pointer;max-width:19rem}
.pf-tv-filterbtn:hover,.pf-tv-filterbtn[aria-expanded="true"]{background:var(--chip);color:var(--text)}
.pf-tv-filterbtn svg{width:20px;height:20px;flex:none;fill:none;stroke:currentColor;stroke-width:1.9;
  stroke-linecap:round}
.pf-tv-filterbtn span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-tv-filterpanel{position:absolute;top:calc(100% + 8px);right:0;z-index:20;min-width:17rem;
  max-height:60vh;overflow-y:auto;background:var(--surface);border:1px solid var(--line-2);
  border-radius:15px;box-shadow:var(--shadow);padding:10px}
.pf-tv-filterhead{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;
  color:var(--text-3);padding:4px 8px 8px}
.pf-tv-filteropt{display:flex;align-items:center;gap:11px;padding:9px 8px;border-radius:10px;
  font-size:16px;color:var(--text);cursor:pointer}
.pf-tv-filteropt:hover{background:var(--surface-2)}
.pf-tv-filteropt input{width:19px;height:19px;flex:none;accent-color:var(--brand);cursor:pointer}
.pf-tv-filteractions{border-top:1px solid var(--line);margin-top:6px;padding-top:6px}
.pf-tv-filterall{width:100%;padding:9px;border:0;border-radius:10px;background:transparent;
  color:var(--brand);font-family:inherit;font-size:15px;font-weight:600;cursor:pointer}
.pf-tv-filterall:hover{background:var(--surface-2)}
.pf-tv-tile{background:var(--surface);border:1px solid var(--line);border-left:5px solid var(--text-3);
  border-radius:14px;padding:12px 14px;margin-bottom:11px;box-shadow:var(--shadow-sm)}
.pf-tv-tile.overdue{border-left-color:var(--danger)}
.pf-tv-tile.today{border-left-color:var(--warn)}
.pf-tv-num{font-size:20px;font-weight:700;color:var(--brand);font-variant-numeric:tabular-nums}
.pf-tv-meta{font-size:14px;color:var(--text-2);font-weight:500}
.pf-tv-ttl{font-size:16px;color:var(--text);margin-top:3px}
.pf-tv-due{font-size:15px;font-weight:600;margin-top:5px;color:var(--text-2)}
.pf-tv-due.overdue{color:var(--danger)}
.pf-tv-loading{color:var(--text-3);font-size:18px;padding:18px 4px}

/* ============ WALL BOARDS (production / dispatch / design: counter cards + columns) ============
   Shared by all three displays. These must never scroll or reflow: the board locks to the viewport and
   wall-board.js trims each column to what fits. Every region below is either fixed-height (head,
   counters, "+N more" footer) or the single flex-shrinking part (.pf-tv-list) — that division is what
   makes the measured cap correct, so keep it if you restyle. */
.pf-tv-board{height:100vh;min-height:0;display:flex;flex-direction:column;overflow:hidden;gap:14px}
.pf-tv-board .pf-tv-head{margin-bottom:0;flex:0 0 auto}

/* Five cards on most boards, six on apparel (one per column), so the renderer sets the count. They stay on
   ONE row at every width, with the same gap as the columns, so each card sits over its column. Narrower
   screens shrink the cards rather than wrap them (Ryan, 2026-09-22). */
.pf-tv-counters{flex:0 0 auto;display:grid;grid-template-columns:repeat(var(--pf-tv-countercount,5),1fr);
  gap:14px}
.pf-tv-counter{background:var(--surface);border:1px solid var(--line);border-top:5px solid var(--text-3);
  border-radius:16px;padding:12px 18px;box-shadow:var(--shadow-sm)}
.pf-tv-counter-n{font-size:44px;line-height:1.05;font-weight:800;color:var(--text);
  font-variant-numeric:tabular-nums}
.pf-tv-counter-l{font-size:15px;font-weight:600;color:var(--text-2);text-transform:uppercase;
  letter-spacing:.04em;margin-top:2px}
.pf-tv-counter[data-tone="danger"]{border-top-color:var(--danger)}
.pf-tv-counter[data-tone="danger"] .pf-tv-counter-n{color:var(--danger)}
.pf-tv-counter[data-tone="warn"]{border-top-color:var(--warn)}
.pf-tv-counter[data-tone="warn"] .pf-tv-counter-n{color:var(--warn)}
.pf-tv-counter[data-tone="info"]{border-top-color:var(--brand-2)}
.pf-tv-counter[data-tone="brand"]{border-top-color:var(--brand)}
.pf-tv-counter[data-tone="ok"]{border-top-color:var(--ok)}
.pf-tv-counter[data-tone="ok"] .pf-tv-counter-n{color:var(--ok)}

/* Column count varies by board — five on production and design, one per active dispatch status on
   freight — so the track count is set by the renderer rather than hard-coded here. */
.pf-tv-cols{flex:1 1 auto;min-height:0;display:grid;gap:14px;
  grid-template-columns:repeat(var(--pf-tv-colcount,5),1fr)}
/* min-width:0 is load-bearing, not tidiness: a grid track's automatic minimum is its min-content, and
   the nowrap job titles below make that wider than 1fr on a smaller screen. Without it the last column
   is pushed past the edge and — because .pf-tv-board clips rather than scrolls — silently disappears. */
.pf-tv-col{min-height:0;min-width:0;display:flex;flex-direction:column;background:var(--surface-2);
  border:1px solid var(--line);border-radius:18px;padding:12px 12px 8px}
/* A heading wraps to a second line rather than being cut off: "QUEUED — RE…" hides the very word that tells
   two columns apart. wall-board.js levels the headings, so every column's first tile starts at one height. */
.pf-tv-col>h2{flex:0 0 auto;font-size:15px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;
  color:var(--text-2);display:flex;justify-content:space-between;align-items:baseline;gap:8px;
  margin:2px 2px 10px}
.pf-tv-col>h2>span:first-child{overflow:hidden;line-height:1.25;display:-webkit-box;
  -webkit-box-orient:vertical;-webkit-line-clamp:2}
.pf-tv-col>h2 .cnt{flex:0 0 auto;font-size:14px;background:var(--chip);color:var(--text-2);
  border-radius:999px;padding:2px 12px;font-variant-numeric:tabular-nums}
/* Accent comes from the board's own tone, not the column name — freight columns are named by the
   shop's dispatch statuses and can be renamed at will. */
.pf-tv-col[data-tone="danger"]>h2{color:var(--danger)}
.pf-tv-col[data-tone="danger"]>h2 .cnt{background:var(--danger-soft);color:var(--danger)}
.pf-tv-col[data-tone="warn"]>h2{color:var(--warn)}
.pf-tv-col[data-tone="warn"]>h2 .cnt{background:var(--warn-soft);color:var(--warn)}
.pf-tv-col[data-tone="ok"]>h2{color:var(--ok)}
.pf-tv-col[data-tone="ok"]>h2 .cnt{background:var(--ok-soft);color:var(--ok)}

/* The one shrinking region: overflow is clipped rather than scrolled, because on a wall screen a
   scrollbar is a promise nobody can keep — the footer count is how the hidden work gets reported. */
.pf-tv-list{flex:1 1 auto;min-height:0;overflow:hidden;list-style:none;margin:0;padding:0}
/* Every row inside a tile truncates rather than widening it — see the min-width note above. */
.pf-tv-board .pf-tv-tile{min-width:0;margin-bottom:9px;padding:9px 12px;border-left-width:5px;
  border-left-color:var(--ok)}
.pf-tv-board .pf-tv-tile.overdue{border-left-color:var(--danger)}
.pf-tv-board .pf-tv-tile.held{border-left-color:var(--text-3);opacity:.72}
.pf-tv-tile-top{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.pf-tv-board .pf-tv-num{font-size:19px}
.pf-tv-board .pf-tv-meta{font-size:14px;margin-top:1px;display:flex;gap:7px;align-items:baseline;
  min-width:0}
.pf-tv-cust{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-tv-board .pf-tv-ttl{font-size:15px;margin-top:2px;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.pf-tv-dept{flex:0 0 auto;font-size:12px;color:var(--text-3);font-weight:600;text-transform:uppercase;
  letter-spacing:.03em}
.pf-tv-foot{display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin-top:5px}
.pf-tv-status{flex:1 1 auto;min-width:0;font-size:13px;font-weight:600;color:var(--text-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-tv-board .pf-tv-due{flex:0 0 auto;font-size:14px;margin-top:0;white-space:nowrap}
.pf-tv-flag{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;
  border-radius:999px;padding:2px 8px}
.pf-tv-flag.rush{background:var(--danger);color:#fff}
.pf-tv-flag.hold{background:var(--chip);color:var(--text-2)}
.pf-tv-flag.out{background:var(--warn-soft);color:var(--warn)}
/* Garment icons ride in the tile's existing flag row, sized to the pills beside them so they add no
   height — on a wall, height is measured in jobs shown. The truck shows only when something needs
   attention; the warehouse also shows green once every garment is here. */
.pf-tv-garment{width:19px;height:19px;flex:none;fill:none;stroke:currentColor;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
.pf-tv-garment[data-tone="warn"]{color:var(--warn)}
.pf-tv-garment[data-tone="danger"]{color:var(--danger)}
.pf-tv-garment[data-tone="ok"]{color:var(--ok)}
.pf-tv-empty{flex:1 1 auto;color:var(--text-3);font-size:16px;padding:6px 2px}
.pf-tv-more{flex:0 0 auto;font-size:14px;font-weight:700;color:var(--text-2);text-align:center;
  padding:7px 0 3px;border-top:1px dashed var(--line-2);margin-top:4px}

/* Narrower/portrait wall screens scale the board down rather than dropping columns. Hiding one would
   hide work with nothing to say so — and on the freight board a missing column is a missing stage. */
@media(max-width:1500px){
  .pf-tv-counter{padding:10px 14px}
  .pf-tv-counter-n{font-size:36px}
  .pf-tv-counter-l{font-size:13px}
  .pf-tv-board .pf-tv-num{font-size:17px}
  .pf-tv-board .pf-tv-ttl,.pf-tv-board .pf-tv-meta{font-size:13px}
  .pf-tv-board .pf-tv-due,.pf-tv-status{font-size:12px}
  .pf-tv-col{padding:10px 9px 6px}
  .pf-tv-board .pf-tv-tile{padding:7px 9px;margin-bottom:7px}
}
@media(max-width:1100px){
  .pf-tv-cols{gap:9px}
  .pf-tv-counters{gap:9px}
  .pf-tv-counter{padding:8px 10px}
  .pf-tv-counter-n{font-size:30px}
  .pf-tv-counter-l{font-size:11.5px;letter-spacing:.02em}
  .pf-tv-col>h2{font-size:13px}
  .pf-tv-board .pf-tv-num{font-size:15px}
}
/* ============ PRESS WALL ============ */
/* One card per press, sized to fill the screen rather than to fit a list. The job boards are read for
   detail by someone who walked up to them; this one has to be legible from the far side of the room,
   because its whole purpose is that nobody has to walk over to find out whether the press stopped. */
/* Wider gutters than the job boards, and a wider gap between cards. Both are for the halo: the board
   clips at its own edge, so a card pressed against it has its light cut off square instead of fading
   out, and two cards 18px apart light each other. */
.pf-tv-presses{padding:22px 36px 32px}
.pf-tvp-grid{flex:1 1 auto;min-height:0;display:grid;gap:34px;
  grid-template-columns:repeat(var(--pf-tvp-count,2),1fr);grid-auto-rows:1fr}
/* The card is a daylight panel (see .pf-daylight) on a dark board, so its own background is opaque
   white and the tone wash is painted over it as an image rather than replacing it — a translucent
   --warn-soft set as the background colour would composite against the dark page behind and come back
   as mud. */
.pf-tvp-card{--pf-tvp-wash:transparent;--pf-tvp-beat:6s;
  --pf-tvp-glow:transparent;--pf-tvp-ring:var(--line-2);
  position:relative;min-width:0;min-height:0;overflow:hidden;display:flex;flex-direction:column;gap:14px;
  background-color:var(--surface);
  background-image:linear-gradient(var(--pf-tvp-wash),var(--pf-tvp-wash));
  border:0;border-radius:22px;padding:26px;
  /* The lit state is the base, not a keyframe: the global prefers-reduced-motion rule kills every
     animation on the page, and a card that fell back to unlit would lose the state entirely. */
  box-shadow:0 0 0 3px var(--pf-tvp-ring),0 0 44px 2px var(--pf-tvp-glow),
    inset 0 0 70px -22px var(--pf-tvp-glow-in,var(--pf-tvp-glow));
  animation:pf-tvp-halo var(--pf-tvp-beat) ease-in-out infinite}

/* The card is lit from behind rather than badged along the top. A wall is glanced at, so the state has
   to be readable before anything on the card is read — but the old bar blinked, and a blinking bar on a
   press that is merely low on magenta reads as an emergency. Light does the same job at the same
   distance without shouting: the ring holds the colour steady and the halo breathes around it, slowly
   enough to look like something running rather than something alarming. It is also the shape of the
   rest of PrintFlow, which is soft edges and glow rather than warning stripes.

   The ring is a shadow rather than a border so it costs no layout: it can thicken with the tone without
   the card's contents moving a pixel.

   The glow is cast both ways, and the inner half is the one that has to work. On a single-press board
   the card is very nearly the whole screen, so there is only a gutter for an outer halo to live in and
   the board clips whatever spills past it; the light that actually reads at that size is the colour
   bleeding in from the card's own rim. On a two-up board it is the reverse — the outer halo has room
   and does most of the work. Casting both means one rule covers a wall showing one press and a wall
   showing four, which is the whole difficulty with this board. */
@keyframes pf-tvp-halo{
  0%,100%{box-shadow:0 0 0 3px var(--pf-tvp-ring),0 0 24px -6px var(--pf-tvp-glow),
    inset 0 0 40px -26px var(--pf-tvp-glow-in,var(--pf-tvp-glow))}
  50%{box-shadow:0 0 0 3px var(--pf-tvp-ring),0 0 64px 8px var(--pf-tvp-glow),
    inset 0 0 100px -18px var(--pf-tvp-glow-in,var(--pf-tvp-glow))}
}
/* The status half grows into whatever the supplies below it do not want, and centres what it holds
   inside that. Top-aligned, a card taller than its contents - which is every card, once the history
   strip that used to fill the gap was dropped - leaves a band of empty white between the job panel and
   the toner bars that reads as something having failed to load. "safe" so a card that is somehow too
   short clips at the bottom like a normal one rather than losing the press's name off the top. */
.pf-tvp-main{min-width:0;flex:1 1 auto;display:flex;flex-direction:column;gap:14px;
  justify-content:safe center}

.pf-tvp-top{display:flex;align-items:baseline;justify-content:space-between;gap:18px;flex-wrap:wrap}
/* Both scale with the viewport: the same board runs on a 1080p landscape TV and a 4K portrait one, and
   a fixed size that suits either wastes half of the other. */
.pf-tvp-name{margin:0;min-width:0;font-size:clamp(26px,2.6vw,46px);font-weight:800;letter-spacing:-.02em;
  color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-tvp-state{flex:0 0 auto;font-size:clamp(24px,2.4vw,42px);font-weight:800;letter-spacing:-.02em;
  color:var(--text-3);white-space:nowrap}
/* The press's own words. Allowed two lines and no more: "Paper jam in tray 2" is the most useful string
   on the card, but a Fiery can return a paragraph, and a card that grows to fit one would push the
   toner off the screen. */
.pf-tvp-detail{margin:0;font-size:clamp(16px,1.35vw,24px);font-weight:600;color:var(--text-2);
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}

/* What is running, and what came off before it. Kept as one block so the second line reads as the
   answer to the first: "nothing printing" alone is the same picture as a press nobody sends work to,
   and "last printed, 20 minutes ago" is what makes it a gap between jobs instead. */
.pf-tvp-jobs{min-width:0;display:flex;flex-direction:column;gap:8px}

/* Set off from the status above it, because when a press IS running this is the thing anyone crossing
   the room actually wants to read. The progress bar only appears with a live job. */
.pf-tvp-job{min-width:0;display:flex;flex-direction:column;gap:8px;
  border-left:4px solid var(--brand);background:var(--chip);border-radius:0 10px 10px 0;
  padding:12px 16px}
.pf-tvp-job-h{font-size:clamp(11px,0.8vw,14px);font-weight:700;text-transform:uppercase;
  letter-spacing:.09em;color:var(--text-3)}
.pf-tvp-job-t{min-width:0;font-size:clamp(16px,1.35vw,24px);font-weight:600;color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-tvp-job-r{display:flex;align-items:center;gap:14px;min-width:0}
.pf-tvp-job-bar{flex:1 1 auto;min-width:2rem;height:12px;border-radius:999px;background:var(--surface-2);
  overflow:hidden}
.pf-tvp-job-bar>i{display:block;height:100%;background:var(--brand)}
.pf-tvp-job-p{flex:0 0 auto;font-size:clamp(15px,1.2vw,22px);font-weight:800;color:var(--brand);
  font-variant-numeric:tabular-nums}
/* An idle press keeps the panel but loses the accent: the row still has to be there, or "last printed"
   below it would be answering a question nothing on the card asked. */
.pf-tvp-job.idle{border-left-color:var(--line-2);background:transparent;padding-left:12px}
.pf-tvp-job.idle .pf-tvp-job-t{color:var(--text-3);font-weight:500}

/* Last printed. One line, indented under the running job, and quieter than it — it is context for what
   the press is doing now, not a second headline. */
.pf-tvp-last{display:flex;align-items:baseline;gap:10px;min-width:0;padding-left:16px;
  font-size:clamp(13px,1.05vw,19px);color:var(--text-2)}
.pf-tvp-last-h{flex:0 0 auto;font-size:0.82em;font-weight:700;text-transform:uppercase;
  letter-spacing:.08em;color:var(--text-3)}
.pf-tvp-last-t{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-tvp-last-a{flex:0 0 auto;margin-left:auto;color:var(--text-3);white-space:nowrap}

/* Supplies sit at the bottom of the card so they land in the same place on every press, whatever else
   each one had to say above them. Two columns on a wide screen, stacked when the card is narrow. */
.pf-tvp-supplies{margin-top:auto;display:grid;gap:14px 26px;grid-template-columns:1fr 1fr}
@media(max-width:1400px){.pf-tvp-supplies{grid-template-columns:1fr}}
.pf-tvp-toners{display:flex;flex-direction:column;gap:9px}
.pf-tvp-trays{display:flex;flex-direction:column;gap:9px;min-width:0}

/* A tray row carries a fourth column the toner rows do not: what is loaded in it. */
.pf-tvp-tray{display:flex;align-items:center;gap:10px;min-width:0}
.pf-tvp-tray-n{flex:0 0 4.5rem;font-size:clamp(13px,1vw,18px);color:var(--text-2);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.pf-tvp-tray-bar{flex:1 1 auto;min-width:2rem;height:14px;border-radius:999px;background:var(--chip);
  overflow:hidden}
.pf-tvp-tray-bar>i{display:block;height:100%;background:var(--text-3)}
.pf-tvp-tray-v{flex:0 0 3rem;text-align:right;font-size:clamp(13px,1vw,18px);font-weight:700;
  color:var(--text-2);font-variant-numeric:tabular-nums}
.pf-tvp-tray-s{flex:0 0 auto;max-width:11rem;font-size:clamp(12px,0.85vw,15px);color:var(--text-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-tvp-toner{display:flex;align-items:center;gap:14px}
.pf-tvp-toner-n{flex:0 0 7.5rem;font-size:clamp(13px,1vw,18px);color:var(--text-2);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.pf-tvp-toner-bar{flex:1 1 auto;height:14px;border-radius:999px;background:var(--chip);overflow:hidden}
.pf-tvp-toner-bar>i{display:block;height:100%}
.pf-tvp-toner-v{flex:0 0 3.5rem;text-align:right;font-size:clamp(13px,1vw,18px);font-weight:700;
  color:var(--text-2);font-variant-numeric:tabular-nums}
/* A low cartridge is called out on the number, not the bar: the bar is already the press's own colour,
   and recolouring it red would say the wrong cartridge is low. */
.pf-tvp-toner.low .pf-tvp-toner-v,.pf-tvp-toner.low .pf-tvp-toner-n{color:var(--danger);font-weight:800}

.pf-tvp-foot{display:flex;align-items:baseline;justify-content:space-between;gap:14px;flex-wrap:wrap;
  border-top:1px solid var(--line);padding-top:12px;font-size:clamp(13px,1vw,18px);color:var(--text-3)}
/* The alert line arrives worded ("In error for 17 hours") and takes the card's own colour, so the two
   halves of the footer cannot be confused: this one is why someone is being called, the one opposite is
   the heartbeat. */
.pf-tvp-since{font-weight:700;color:var(--text-2)}
.pf-tvp-card[data-tone="warn"] .pf-tvp-since,
.pf-tvp-card[data-tone="blind"] .pf-tvp-since{color:var(--warn)}
.pf-tvp-card[data-tone="danger"] .pf-tvp-since{color:var(--danger)}
.pf-tvp-seen{margin-left:auto}

/* Tones: green nothing-wrong, orange needs-someone, red stopped. The breath is part of the signal and
   quickens with the trouble, but all three are slow — the fastest is a two-second swell, because the
   difference between "someone should look at this" and "stop what you are doing" is carried by the
   colour, and a light that flashes says the second thing about the first. The two blind states keep a
   tone of their own rather than sharing red with a fault: both are urgent, but a fault is the
   engineer's problem and a press nobody can see is IT's, and the difference should be readable from
   across the room without reading the label.

   The glow colours are opaque-ish where the washes are not: a box-shadow's blur thins a colour out
   over its whole radius, so an alpha low enough to look right as a background disappears entirely as
   light. */
/* A healthy press gets the halo but barely any of the inward bleed. The inner glow tints the card, and
   on the tones that matter that is a feature — it is the same signal as the wash. On a press with
   nothing wrong with it, a green cast over a panel whose entire job is to show toner colours honestly
   is exactly the fault the daylight palette was added to fix. Lit from outside, white on the inside. */
.pf-tvp-card[data-tone="ok"]{--pf-tvp-ring:var(--ok);
  --pf-tvp-glow:rgba(18,161,122,.5);--pf-tvp-glow-in:rgba(18,161,122,.22);--pf-tvp-beat:7s}
.pf-tvp-card[data-tone="ok"] .pf-tvp-state{color:var(--ok)}
/* The washes are their own values rather than the --*-soft tokens, and much fainter than those. Those
   are sized to show on a dark surface; here the tint covers a whole wall-sized white panel, and at .12
   it turns the card cream — which is the exact thing the daylight palette is here to undo, since a
   toner swatch is only honest against white. Enough to tell two cards apart at a glance, no more. */
.pf-tvp-card[data-tone="warn"]{--pf-tvp-ring:var(--warn);
  --pf-tvp-glow:rgba(223,133,0,.55);--pf-tvp-wash:rgba(223,133,0,.05);--pf-tvp-beat:4s}
.pf-tvp-card[data-tone="warn"] .pf-tvp-state{color:var(--warn)}
.pf-tvp-card[data-tone="danger"]{--pf-tvp-ring:var(--danger);
  --pf-tvp-glow:rgba(229,72,77,.6);--pf-tvp-wash:rgba(229,72,77,.05);--pf-tvp-beat:2s}
.pf-tvp-card[data-tone="danger"] .pf-tvp-state{color:var(--danger)}
/* Lit, but with a gap in the ring rather than a solid one: the card is not reporting a state, it is
   reporting that it has no state to report, and an unbroken outline reads as one. A shadow cannot be
   dashed, so the ring becomes a real dashed border here — the only tone that pays the 3px of layout. */
.pf-tvp-card[data-tone="blind"]{--pf-tvp-ring:transparent;
  --pf-tvp-glow:rgba(223,133,0,.4);--pf-tvp-wash:var(--surface-2);--pf-tvp-beat:4s;
  border:3px dashed var(--warn);padding:23px}
.pf-tvp-card[data-tone="blind"] .pf-tvp-state{color:var(--warn)}
/* Never reported at all — a setup problem, not a press. Unlit and still: nothing here is happening. */
.pf-tvp-card[data-tone="idle"]{--pf-tvp-ring:var(--line-2);opacity:.72;animation:none}
.pf-tvp-empty{grid-column:1/-1;align-self:center;text-align:center;color:var(--text-3);
  font-size:clamp(18px,1.6vw,28px);font-weight:600;padding:0 12%}

/* ---- A single press ----
   Not a two-up card with one side missing. Stacked, one press leaves a dead band through the middle of
   a very wide card: the status collapses to the top, `margin-top:auto` pins the supplies to the bottom,
   and the height between them belongs to nothing. So the card turns sideways instead - status on the
   left, toner and paper on the right - and the type grows into the room that frees up. The shop runs one
   press today and will run two; this is what makes the wall right for both rather than for the plural. */
@media(min-width:1100px){
  .pf-tvp-grid[data-count="1"] .pf-tvp-card{
    display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);
    column-gap:clamp(28px,3.5vw,64px);row-gap:20px;
    align-content:start;padding:clamp(26px,2.6vw,44px) clamp(30px,3vw,52px)}
  /* The card fills the board and the columns fill the card, so the footer lands at the bottom of the
     screen rather than halfway up a 1080p one - which is what the first attempt did. */
  .pf-tvp-grid[data-count="1"]{align-content:stretch}
  .pf-tvp-grid[data-count="1"] .pf-tvp-card{align-content:stretch;
    grid-template-rows:minmax(0,1fr) auto}
  /* Centring comes from .pf-tvp-main; this only places it and opens the gaps up for the larger type. */
  .pf-tvp-grid[data-count="1"] .pf-tvp-main{grid-column:1;grid-row:1;gap:22px;min-height:0}
  /* Spans both rows so the bars sit beside the status rather than under it, and starts at the top so a
     press with little to say does not stretch its toner down the screen. */
  /* One column, two groups, each spread over half the height. On a board showing a single press the
     supplies have a whole side of a TV to themselves - packed tight at the top they leave a void under
     them, and these are the rows people read from furthest away. */
  /* Each group takes half the height and spreads its own rows through it. Pushing the two GROUPS apart
     instead (space-between) opens a hole between the toner and the paper, which reads as two unrelated
     panels rather than one column of supplies. */
  .pf-tvp-grid[data-count="1"] .pf-tvp-supplies{grid-column:2;grid-row:1;margin-top:0;
    grid-template-columns:1fr;grid-template-rows:1fr 1fr;align-content:stretch;gap:20px}
  .pf-tvp-grid[data-count="1"] .pf-tvp-toners,
  .pf-tvp-grid[data-count="1"] .pf-tvp-trays{min-height:0;justify-content:space-evenly}
  .pf-tvp-grid[data-count="1"] .pf-tvp-foot{grid-column:1 / -1;grid-row:2}
  /* A running job is the hero on a single-press board - bigger than the fault line above it, because a
     press that is printing has nothing wrong with it and this is the only thing changing on screen.
     The panel keeps its natural height rather than stretching to fill the column - a half-empty grey
     box is worse than white space, because it looks like a panel with something missing from it. The
     column centres instead; see .pf-tvp-main above. */
  .pf-tvp-grid[data-count="1"] .pf-tvp-jobs{gap:14px}
  .pf-tvp-grid[data-count="1"] .pf-tvp-job{padding:24px 30px;gap:16px}
  .pf-tvp-grid[data-count="1"] .pf-tvp-job-h{font-size:clamp(13px,1vw,17px)}
  .pf-tvp-grid[data-count="1"] .pf-tvp-job-t{font-size:clamp(24px,2.2vw,40px);white-space:normal;
    display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical}
  .pf-tvp-grid[data-count="1"] .pf-tvp-job-bar{height:22px}
  .pf-tvp-grid[data-count="1"] .pf-tvp-job-p{font-size:clamp(22px,1.9vw,34px)}
  .pf-tvp-grid[data-count="1"] .pf-tvp-last{font-size:clamp(16px,1.35vw,24px);padding-left:24px;gap:14px}

  /* The room the sideways layout frees is spent on the two things read from across the room. */
  .pf-tvp-grid[data-count="1"] .pf-tvp-name{font-size:clamp(34px,3.6vw,62px)}
  .pf-tvp-grid[data-count="1"] .pf-tvp-state{font-size:clamp(30px,3.2vw,56px)}
  .pf-tvp-grid[data-count="1"] .pf-tvp-detail{font-size:clamp(18px,1.6vw,30px);-webkit-line-clamp:3;
    line-clamp:3}
  .pf-tvp-grid[data-count="1"] .pf-tvp-top{display:block}
  .pf-tvp-grid[data-count="1"] .pf-tvp-state{display:block;margin-top:4px}

  /* Taller bars, because they are now the full height of half a wall rather than a strip on a card. */
  .pf-tvp-grid[data-count="1"] .pf-tvp-toner-bar,
  .pf-tvp-grid[data-count="1"] .pf-tvp-tray-bar{height:20px}
  .pf-tvp-grid[data-count="1"] .pf-tvp-toner,
  .pf-tvp-grid[data-count="1"] .pf-tvp-tray{gap:16px}
  .pf-tvp-grid[data-count="1"] .pf-tvp-toner-n,
  .pf-tvp-grid[data-count="1"] .pf-tvp-toner-v,
  .pf-tvp-grid[data-count="1"] .pf-tvp-tray-n,
  .pf-tvp-grid[data-count="1"] .pf-tvp-tray-v,
  .pf-tvp-grid[data-count="1"] .pf-tvp-tray-s{font-size:clamp(15px,1.25vw,22px)}
  .pf-tvp-grid[data-count="1"] .pf-tvp-toner-n{flex-basis:9rem}
  .pf-tvp-grid[data-count="1"] .pf-tvp-tray-n{flex-basis:6rem}
  .pf-tvp-grid[data-count="1"] .pf-tvp-tray-s{max-width:14rem}
}
@media(max-width:1100px){
  .pf-tvp-grid{grid-template-columns:1fr;grid-auto-rows:auto;gap:12px}
  .pf-tvp-card{padding:16px 18px;gap:10px}
  .pf-tvp-toner-n{flex-basis:5.5rem}
}

/* ============ DISPLAYS PICKER ============ */
.pf-display-card{transition:box-shadow .15s ease,transform .15s ease}
a.pf-display-card:hover{box-shadow:var(--shadow);transform:translateY(-1px)}
.pf-display-icon{display:inline-flex;align-items:center;justify-content:center;width:42px;height:42px;
  flex:0 0 42px;border-radius:12px;background:var(--cyan-soft);color:var(--brand)}
.pf-display-icon .pf-icon{stroke:currentColor;fill:none;stroke-width:1.7}
.pf-display-card h3{color:var(--text)}
/* Locked: still shown, so staff can see the display exists and ask for access. */
.pf-display-card-locked{opacity:.55;background:var(--surface-2);cursor:not-allowed}
.pf-display-card-locked .pf-display-icon{background:var(--chip);color:var(--text-3)}

@media(max-width:1080px){
  .pf-grid{grid-template-columns:minmax(0,1fr)}
  .pf-ribbon{flex-wrap:wrap}
  .pf-metric{flex:1 1 33%;border-top:1px solid var(--line)}
  .pf-metric:first-child,.pf-metric:nth-child(2),.pf-metric:nth-child(3){border-top:0}
}
/* ============ PHONE / SMALL TABLET SHELL (≤900px) ============
   Above this width the nav is a horizontal link bar. Below it the bar has nowhere to go, so it becomes
   an off-canvas drawer opened by the burger — it used to be `display:none` with no replacement, which
   left a phone with no way to reach anything but the dashboard.

   The drawer *is* .pf-links, restyled: same markup, same role checks. Its hover submenus become sections
   that open under their heading when it is tapped, one at a time (printflow.js). They used to be laid out
   open, all of them at once, which made a drawer of some thirty links you had to scroll to find anything. */
@media(max-width:900px){
  /* Drives the bar, the drawer's height and the press alert's sticky offset together. */
  :root{--pf-nav-h:56px}
  .pf-nav{gap:8px;padding:0 10px}
  .pf-burger{display:grid}
  .pf-brand .pf-logo{height:21px}

  .pf-links{
    display:flex;flex-direction:column;align-items:stretch;gap:2px;
    position:absolute;top:100%;left:0;z-index:2;
    width:min(86vw,330px);height:calc(100vh - var(--pf-nav-h));height:calc(100dvh - var(--pf-nav-h));
    padding:10px 12px 32px;overflow-y:auto;overscroll-behavior:contain;
    background:var(--surface);border-right:1px solid var(--line-2);box-shadow:var(--shadow);
    transform:translateX(-102%);visibility:hidden;
    transition:transform .22s ease,visibility .22s ease;
  }
  .pf-nav.pf-navopen .pf-links{transform:none;visibility:visible}
  .pf-navscrim{display:block}

  /* position:static so the submenu below stacks in flow; the ::after is the desktop hover bridge, which
     would otherwise stretch across the whole bar once its positioned parent is gone. */
  .pf-navitem{position:static;display:flex;flex-direction:column;align-items:stretch}
  .pf-navitem::after{display:none}
  .pf-links .pf-link{width:100%;padding:12px 14px;border-radius:11px;font-size:15px;color:var(--text)}
  .pf-links .pf-link.active{background:var(--cyan-soft);color:var(--brand)}
  .pf-links .pf-link.active::after{display:none}
  /* Closed with display:none rather than a zero height: a closed section's links leave the tab order, and
     the desktop :hover rule has nothing to reveal. That matters on iOS, which spends the first tap on a
     :hover that shows something and only clicks on the second. */
  .pf-submenu{
    display:none;
    position:static;min-width:0;opacity:1;visibility:visible;transform:none;transition:none;
    background:transparent;border:0;border-left:1px solid var(--line-2);border-radius:0;box-shadow:none;
    margin:2px 0 8px 24px;padding:0 0 0 4px;
  }
  .pf-navitem.pf-open>.pf-submenu{display:block}
  .pf-subitem{padding:11px 12px;font-size:14px;color:var(--text-2)}
  .pf-subitem.pf-drawer-only{display:flex}
  .pf-subsep{margin:5px 12px}
  /* A section heading's chevron, turned up while it is open. ::before because the heading's ::after is the
     desktop active underline; order and margin put it at the far end of the row. */
  .pf-navitem>.pf-link::before{
    content:"";order:1;margin-left:auto;flex:none;width:18px;height:18px;
    background:currentColor;opacity:.45;transition:transform .22s ease,opacity .22s ease;
    -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center/contain no-repeat;
    mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center/contain no-repeat;
  }
  .pf-navitem>.pf-link[aria-expanded="true"]::before{transform:rotate(180deg);opacity:.8}

  /* Actions: the gear leaves (Settings has a twin in the account menu) and the running timer shrinks to
     its pulsing stop dot. That is what buys the remaining buttons their full targets instead of pushing
     the avatar off the right edge, which is what was happening. */
  .pf-actions{gap:2px}
  .pf-actions .pf-hide-sm{display:none}
  .pf-iconbtn{width:36px;height:36px}
  .pf-avatar{width:32px;height:32px}
  .pf-timer{flex:none;padding:0;margin-right:2px;border-radius:999px}
  .pf-timer-stop{width:34px;height:34px;justify-content:center;padding:0;gap:0}
  .pf-timer-stoptext{display:none}

  /* A 250px input cannot open beside five buttons, so on a phone it takes the whole bar for as long as
     it is open rather than shoving them past the edge. */
  .pf-search.open{position:absolute;inset:0 10px;z-index:3;background:var(--surface);align-items:center}
  .pf-search.open .pf-search-input{flex:1 1 auto;width:auto;min-width:0;margin-right:6px}

  /* .pf-wrap .card is overflow:hidden, so a table wider than the phone was being clipped rather than
     scrolled and its right-hand columns simply vanished. Tables already inside .table-responsive (the
     list pages, and most of the app) are untouched; this catches the ones sitting bare in a card. */
  .pf-wrap .card>.table,.pf-wrap .card>.card-body>.table{display:block;overflow-x:auto;max-width:100%}

  main.pf-wrap{padding:20px 14px 48px}
}

/* Narrow phones: the wordmark goes, the mark stays. It is the widest item in the bar and the only one
   that is pure identity — everything else is a destination. */
@media(max-width:560px){
  .pf-brand .pf-logo{display:none}
  .pf-brand .pf-mark{display:block}
  .pf-actions{gap:0}
  .pf-iconbtn{width:34px;height:34px}
}

@media(max-width:760px){
  /* Five stations across a phone give each one about 70px, which is not a column. The river is the part
     that cannot survive that, so the decoration goes and the figures reflow into tiles instead. */
  .pf-flowriver{display:none}
  .pf-flow{padding:18px 14px 14px}
  .pf-cap{padding:0 2px 10px}
  .pf-stations{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
  .pf-station{align-items:flex-start;text-align:left;padding:12px 13px;gap:1px;
    background:var(--surface);border:1px solid var(--line);border-radius:14px}
  /* Invoiced · MTD takes the full width as the odd one out — it is the end of the pipeline, so reading
     as a closing total rather than a stranded tile is right. */
  .pf-station:last-child{grid-column:1/-1}
  .pf-station .c{font-size:28px}

  /* Six tiles two-up. The hairlines were written for the three-up layout, so they are restated: at two
     per row it is the third tile that opens a new row, and every odd tile that starts one. */
  .pf-metric{flex:1 1 50%;padding:14px 16px}
  .pf-metric:nth-child(3){border-top:1px solid var(--line)}
  .pf-metric:nth-child(odd){border-left:0}
  .pf-metric .v{font-size:25px;margin-top:6px}
  .pf-frow{grid-template-columns:8px minmax(0,1.4fr) 96px 30px}
  .pf-frow .pf-stage{display:none}
  /* An activity line that no longer fits gives up the end of its description, not its time. Truncating the
     line as a whole cut the timestamp off first, and the time is half of what the line is for. */
  .pf-fitem .t-split{display:flex;gap:.3em}
  .pf-fitem .t-split>span:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis}
  .pf-fitem .t-split>span:last-child{flex:none}

  /* Production load goes to two lines a row. On one line the name had a fixed 104-150px, shared with its
     red "N overdue" note, so on a phone every department was cut to three or four letters. The name and
     note now take the first line with the share and count beside them, and the bar runs the full width
     underneath, which is also the only width at which its proportion can be read. */
  .pf-lrow{display:grid;grid-template-columns:minmax(0,1fr) auto auto;grid-template-areas:"n pct c" "track track track";
    align-items:baseline;column-gap:12px;row-gap:5px;padding:7px 0}
  .pf-lrow .n{grid-area:n}
  .pf-lrow .track{grid-area:track;align-self:center}
  .pf-lrow .pct{grid-area:pct;min-width:32px}
  .pf-lrow .c{grid-area:c;min-width:22px}
}

/* Card interiors on a phone. The generous desktop padding was eating roughly a third of a 390px screen
   before any content got a say, which is most of why the cards read as badly laid out. */
@media(max-width:560px){
  .pf-head h1{font-size:23px}
  .pf-card-h{padding:15px 16px 12px}
  .pf-card-h h3{font-size:15px}
  .pf-focus,.pf-list,.pf-feed{padding-left:4px;padding-right:4px}
  .pf-load{padding:6px 16px 16px}
  /* Assignee initials and the stage chip are the first things to go when the title is down to ~140px:
     both are on the job itself, one tap away. The dot, the name and the due date are the row. */
  .pf-frow{grid-template-columns:8px minmax(0,1fr) auto;gap:10px;padding:11px 10px;align-items:baseline}
  .pf-frow .pf-av{display:none}
  /* Every focus row is exactly two lines: number and customer, then item and flags. Wrapping made rows two
     to four lines tall, stranded a lone "Reprint" on a line of its own and left the due date floating
     halfway down. The text truncates instead, and the dot and due date sit level with the job number. */
  .pf-frow .pf-udot{align-self:start;margin-top:7px}
  .pf-fmain{min-width:0}
  .pf-fmain b{display:flex;align-items:baseline;min-width:0}
  .pf-fmain b>span,.pf-csub>span:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .pf-jn,.pf-fmini{flex:none}
  .pf-item{padding:11px 10px;gap:10px}
  /* The customer is what the row is about; the rep beside it is a footnote. Uncapped, a long staff name
     took the width and folded the customer's name into a column a word wide. */
  .pf-item .right{max-width:42%}
  .pf-item .wait,.pf-item .r2{overflow:hidden;text-overflow:ellipsis}
  /* A long card meta ("52 active jobs · 7 departments") drops under the title as one line instead of
     folding in half beside it. */
  .pf-grid .pf-card-h{flex-wrap:wrap;row-gap:2px}

  .pf-wrap h1.h3{font-size:22px}
  .pf-wrap .card-body{padding:16px}
  .pf-wrap .card-header{padding:13px 16px}
  .pf-wrap .table>thead>tr>th{padding:10px 12px}
  .pf-wrap .table>tbody>tr>td{padding:11px 12px}
  /* The scope/display chips push to the far right on desktop; on a wrapped bar that strands them on a
     line of their own. */
  .pf-lb-right{margin-left:0}
}
@media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* ===== Install calendar (SI-2) ===== */
.installcal-table{table-layout:fixed}
.installcal-table th,.installcal-table td{vertical-align:top;padding:.4rem}
.installcal-crewcol{width:150px;white-space:nowrap;font-weight:600}
.installcal-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:6px;vertical-align:middle}
.installcal-block{display:block;text-decoration:none;color:inherit;border:1px solid var(--bs-border-color);border-left:4px solid var(--bs-primary);border-radius:6px;padding:4px 6px;margin-bottom:4px;font-size:.78rem;line-height:1.25;background:var(--bs-tertiary-bg)}
.installcal-block:hover{filter:brightness(1.05);border-color:var(--bs-primary)}
.installcal-time{display:block;font-weight:600}
.installcal-wx{display:inline-block;width:8px;height:8px;border-radius:50%;margin-left:5px;vertical-align:middle}
.installcal-inst{display:block}
.installcal-cust{display:block;color:var(--bs-secondary-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.installcal-clashtag{display:inline-block;font-size:.68rem;color:#fff;background:var(--bs-danger,#dc3545);border-radius:4px;padding:0 5px;margin-top:3px}

/* ===== Quote line — money strip =====
   The sell, the charges, the total, the cost and the margin as one row of hairline-separated
   tiles. Replaces a pair of two-column <dl>s whose long labels wrapped into their own figures.
   auto-fit means it reflows to two rows on a narrow screen instead of crushing the numbers. */
.pfline-money{display:grid;grid-template-columns:repeat(auto-fit,minmax(142px,1fr));
  gap:1px;background:var(--line-2);border:1px solid var(--line-2);border-radius:14px;overflow:hidden}
.pfline-money .pfm{background:var(--surface);padding:12px 14px;display:flex;flex-direction:column;gap:3px;min-width:0}
.pfline-money .l{font-size:10.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--text-2)}
.pfline-money .v{font-size:21px;font-weight:400;letter-spacing:-.02em;line-height:1.15;color:var(--text)}
.pfline-money .d{font-size:11.5px;color:var(--text-3);line-height:1.3}
/* The two figures the question actually turns on — what it sells for, and what is left. */
.pfline-money .pfm-key{background:var(--surface-2)}
.pfline-money .pfm-key .v{font-weight:600}
.pfline-money .v.neg{color:var(--danger)}
.pfline-money .v.pos{color:var(--ok)}
@media(max-width:640px){
  .pfline-money{grid-template-columns:repeat(auto-fit,minmax(118px,1fr))}
  .pfline-money .pfm{padding:10px 11px}
  .pfline-money .v{font-size:18px}
}

/* ===== Quote line — production spec row =====
   The formula spec groups (imposition, click colour/A3, install, wastage) share one wrapping flex
   row instead of a Bootstrap row each: only a few are on at once, and a row apiece spread four
   small controls down a third of the screen. Groups keep their own d-none toggles. */
.pfspec{display:flex;flex-wrap:wrap;gap:12px 16px;align-items:flex-start}
/* display:contents so a group's fields join the outer row directly and every field is sized the
   same — as a nested flex item the group shrank and wrapped its own contents mid-row. Toggling
   the group to .d-none still hides the whole subtree, display:none beating display:contents. */
.pfspec .pfspec-g{display:contents}
.pfspec .pfspec-f{flex:0 1 190px;min-width:150px}
/* The run size is two boxes and a ×, so it takes a wider slot than one number. */
.pfspec .pfspec-run{flex:0 1 250px;min-width:210px}
/* The live "≈ 132 sheets" readout hangs off the end of the row, on the inputs' baseline. */
.pfspec .pfspec-info{align-self:center;flex:1 1 160px}
.pfspec .pfspec-info .form-text{margin-top:0}

/* ===== Quote line — signage materials (V164) =====
   One row per role — print media, laminate, substrate — each holding as many pickers as the job needs
   (the + adds another of that kind). The label sits to the left so three roles read as one block, and
   drops above its pickers on a phone. Each picker carries what it comes to beside it. */
.sg-roles{display:flex;flex-direction:column;gap:10px}
.sg-role{display:grid;grid-template-columns:112px minmax(0,1fr);gap:4px 12px;align-items:start}
.sg-role-label{font-size:13px;font-weight:600;color:var(--text-2);padding-top:6px}
.sg-rows{display:flex;flex-direction:column;gap:6px;min-width:0}
.sg-row{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:8px;align-items:center}
.sg-readout{font-size:12px;color:var(--text-2);font-variant-numeric:tabular-nums;white-space:nowrap;
  min-width:128px;text-align:right}
.sg-readout.is-warn{color:var(--warn);white-space:normal}
.sg-remove{width:28px;height:28px;display:grid;place-items:center;border:0;border-radius:7px;
  background:none;color:var(--text-3);font-size:17px;line-height:1;cursor:pointer;padding:0;font-family:inherit}
.sg-remove:hover{background:var(--surface-2);color:var(--danger)}
.sg-add{grid-column:2;justify-self:start;background:none;border:0;padding:0;font-size:12.5px;font-weight:600;
  color:var(--brand);cursor:pointer;font-family:inherit}
.sg-add:hover{color:var(--brand-deep)}
.sg-foot{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 18px;margin-top:10px}
.sg-foot .form-check{margin:0}
.sg-foot .form-text{margin:0}
@media (max-width:760px){
  .sg-role{grid-template-columns:minmax(0,1fr)}
  .sg-role-label{padding-top:0}
  .sg-add{grid-column:1}
  .sg-readout{min-width:0}
}

/* ===== pfcombo — type-to-search picker (static/js/pfcombo.js) =====
   The list overlays what is below it rather than pushing the form down, like the till's customer search. */
.pfcombo{position:relative;min-width:0}
.pfcombo-list{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:30;margin:0;padding:5px;
  list-style:none;max-height:300px;overflow-y:auto;background:var(--surface);border:1px solid var(--line);
  border-radius:12px;box-shadow:var(--shadow)}
.pfcombo-opt{display:flex;flex-direction:column;gap:1px;padding:6px 9px;border-radius:8px;cursor:pointer}
.pfcombo-opt:hover,.pfcombo-opt.is-active{background:var(--surface-2)}
.pfcombo-opt[aria-selected="true"] .pfcombo-name{color:var(--brand-deep)}
.pfcombo-name{font-size:13px;font-weight:600;color:var(--text);overflow-wrap:anywhere}
.pfcombo-sub{font-size:11.5px;color:var(--text-3);font-variant-numeric:tabular-nums}
.pfcombo-sub.is-warn{color:var(--warn)}
.pfcombo-none{padding:8px 9px;font-size:12.5px;color:var(--text-3)}
/* A card with a picker near its foot — the purchase order's add-line box — lets the list hang out below it.
   .pf-wrap .card clips for its rounded corners; nothing in such a card paints into them. */
.pf-wrap .card.pf-card-overflow{overflow:visible}
/* A picker in a narrow column opens wider than its box, anchored to the box's right edge. */
.pfcombo.pfcombo-wide .pfcombo-list{left:auto;min-width:min(340px,90vw)}
/* A table whose rows carry a picker (the PO line edit row) stops clipping it on a desktop, where the table fits
   and has nothing to scroll; below that it keeps table-responsive's sideways scroll. */
@media (min-width:992px){.pf-wrap .table-responsive.pf-table-overflow-lg{overflow:visible}}

/* ===== Quote line — apparel size grid =====
   Sizes run ACROSS as columns; quantity, cost, markup, price and line total run down as rows. That is
   the shape an apparel order arrives in — "ten mediums, five larges" — and the shape it is read back
   in on the job ticket, so the grid is scanned left to right in one glance.

   THE WHOLE RUN MUST FIT THE FRAME. table-layout:fixed + width:100% is what does it: the size columns
   divide whatever is left after the two pinned ones and share it equally, however many there are. Auto
   layout cannot do this — it sizes a column to its content, and an <input>'s max-content width is its
   default intrinsic ~20-character box, so nine sizes came out at ~226px each and the grid was twice the
   width of the form. Fixed layout also means min-width on a cell is ignored for column sizing, hence
   none here; the inputs are simply told to fill their cell. */
.apparel-grid-wrap{overflow-x:auto}
.apparel-grid{table-layout:fixed;width:100%;margin-bottom:0;
  /* The floor. Below it the wrapper scrolls rather than squeezing the numbers into illegibility —
     --ap-cols is set from the live column count by the line-form JS, so the floor tracks the run.
     On any normal window this is under 100% and never bites. */
  min-width:calc(9.5rem + var(--ap-cols,9) * 4.2rem)}
.apparel-grid > :not(caption) > * > *{padding:.15rem .2rem}
.apparel-grid input{text-align:center;width:100%;min-width:0;padding:.2rem .15rem;font-size:.8rem}
/* No spinners. They reserve ~16px inside every number input whether or not they are showing, which at
   these column widths is the difference between reading "35.00" and reading "35.(" — and nobody nudges
   a garment cost up a cent at a time with arrows. */
.apparel-grid input[type=number]{appearance:textfield;-moz-appearance:textfield}
.apparel-grid input[type=number]::-webkit-outer-spin-button,
.apparel-grid input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
/* The row labels stay pinned to the left edge — a number that has scrolled away from what it means is
   worse than no number. It needs its own background or the scrolling cells show through underneath,
   and a hairline edge so the pin reads as a boundary rather than a rendering artefact. */
.apparel-grid .apparel-rowlab{position:sticky;left:0;z-index:2;width:5.5rem;
  border-right:1px solid var(--line-2);font-weight:600;font-size:.74rem;color:var(--text-2);
  white-space:nowrap;text-align:right;padding-right:.5rem}
/* The size label IS the column header, and it is editable: a custom garment might run "Youth 8" or
   "OSFA". Its × is parked in the cell's corner rather than sitting in the flow — at these widths a
   button in the row would eat a quarter of the column the size name needs. */
.apparel-grid .apparel-size-cell{position:relative;padding-top:.4rem}
.apparel-grid .apparel-size-head .apparel-label{font-weight:600}
.apparel-grid .apparel-size-head .btn-close{position:absolute;top:.05rem;right:.15rem;
  padding:.15rem;font-size:.5rem;opacity:.3}
.apparel-grid .apparel-size-head .btn-close:hover{opacity:.9}
/* A size the chosen colourway isn't made in. Drawn, so the column run still reads as the garment's
   full size range, but visibly not fillable — a supplier's colour × size grid is sparse (22 colours
   across three bands gives 63 groups, not 66), and quoting the gap puts a quantity on an order nobody
   can supply. Struck through rather than hidden: a missing column reads as "we forgot 5XL". */
.apparel-grid .apparel-size-unavailable{opacity:.45}
.apparel-grid th.apparel-size-unavailable .apparel-label{text-decoration:line-through}
.apparel-grid .apparel-size-unavailable input:disabled{background:var(--surface-2);cursor:not-allowed}
/* Running totals, pinned to the right the way the labels are pinned left, so the two numbers that
   answer "how many and how much" are always on screen. */
.apparel-grid .apparel-total-col{position:sticky;right:0;z-index:2;width:4.5rem;
  border-left:1px solid var(--line-2);text-align:right;white-space:nowrap;
  font-size:.8rem;font-variant-numeric:tabular-nums}
/* Both pinned columns must be OPAQUE, or the size columns scroll visibly through them. This needs the
   .pf-wrap prefix to outrank `.pf-wrap .table > thead > tr > th`, which forces header cells transparent
   — without it the pinned header cell alone stayed see-through and the last size label was drawn on
   top of the word "Total". --surface is the card the grid sits on, so the pins read as part of it. */
.pf-wrap .apparel-grid .apparel-rowlab,
.pf-wrap .apparel-grid .apparel-total-col{background:var(--surface)}
.apparel-grid .apparel-row-line{font-size:.78rem;font-variant-numeric:tabular-nums}
.apparel-grid .apparel-row-line td{text-align:right;padding-right:.35rem}
/* The inc-GST row sits under the (ex-GST) price it restates, so it is drawn as an annotation of that
   row rather than a figure of its own — same alignment, one step back in weight. */
.apparel-grid .apparel-row-inc td,.apparel-grid .apparel-row-inc .apparel-rowlab{color:var(--text-3)}

/* ===== Ex/inc GST readouts =====
   Every price and cost the shop enters is ex GST. These are the same money with GST on it, shown
   beside the figure they restate so a quoter can answer "what's that all up?" without arithmetic —
   and so nobody reads an ex figure as if it were the inc one. Display only: nothing here is stored,
   and the document's GST is still worked out once, by QuoteTotals. */
.pf-inc{color:var(--text-3);font-variant-numeric:tabular-nums}

/* The other direction (V136): what a figure in a COST box would be worth ex GST, shown when the
   chosen supplier is flagged as quoting inclusive. Warmer than .pf-inc on purpose — .pf-inc restates
   a number that is already right, this one is asking whether the number is right at all. */
.pf-exgst{color:var(--bs-warning-text-emphasis,#997404);font-variant-numeric:tabular-nums}

/* ===== Quote line — decoration slots (apparel) =====
   On an apparel line the charges table is relabelled and re-ordered into ShopVox-style slots: the
   setup/origination charge first, then one row per decoration location. It is the same charge rows
   underneath — the slot label is drawn from whether the charge carries a position (V112), not from a
   second kind of record. */
.charge-row .cr-slot{display:inline-block;min-width:5.6rem;font-size:10.5px;font-weight:700;
  letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);vertical-align:1px}

/* ============ COUNTER SALE (till — touch-scaled, like the operator screen) ============
   The till is a surface someone stands at with a customer waiting, so it is deliberately exempt from
   the dense/space-conscious directive that governs the list and form screens: tiles are big enough to
   hit without looking, and the total is big enough to read across the counter. Everything is pf-scoped
   so no other page picks it up. */
.pf-wrap .ctr-head{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;margin-bottom:16px}
.pf-wrap .ctr-note{color:var(--text-3);font-size:13.5px}

/* The completed-sale banner. Shows the SERVER's figures, so it is visually distinct from the running
   total in the sale panel — one is what was charged, the other is what is being rung up. */
.pf-wrap .ctr-done{display:flex;align-items:center;gap:18px;flex-wrap:wrap;background:var(--ok-soft);
  border:1px solid var(--ok);border-radius:14px;padding:14px 18px;margin-bottom:16px}
.pf-wrap .ctr-donemain{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
.pf-wrap .ctr-donelabel{font-weight:700;color:var(--ok)}
.pf-wrap .ctr-donetotal{font-size:26px;font-weight:700;color:var(--text);font-variant-numeric:tabular-nums}
.pf-wrap .ctr-donechange{font-size:15px;color:var(--text-2)}
.pf-wrap .ctr-donechange strong{font-size:19px;color:var(--text);font-variant-numeric:tabular-nums}
.pf-wrap .ctr-doneacts{display:flex;gap:8px;margin-left:auto}

/* Grid on the left, the sale on the right. The sale panel sticks: with a long grid the operator must
   never have to scroll to reach Complete. */
.pf-wrap .ctr-stage{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:18px;align-items:start}
@media (max-width:900px){.pf-wrap .ctr-stage{grid-template-columns:minmax(0,1fr)}}

.pf-wrap .ctr-cat{margin-bottom:18px}
.pf-wrap .ctr-cath{font-size:11.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  color:var(--text-3);margin:0 0 8px}
.pf-wrap .ctr-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(158px,1fr));gap:10px}
.pf-wrap .ctr-tile{display:flex;flex-direction:column;gap:2px;text-align:left;min-height:84px;
  background:var(--surface);border:1px solid var(--line-2);border-radius:14px;padding:12px 14px;
  cursor:pointer;transition:.12s;color:var(--text);font-family:inherit}
.pf-wrap .ctr-tile:hover{border-color:var(--brand);box-shadow:var(--shadow-sm)}
.pf-wrap .ctr-tile:active{transform:scale(.98)}
.pf-wrap .ctr-tilename{font-weight:600;font-size:14px;line-height:1.25}
.pf-wrap .ctr-tileprice{font-size:19px;font-weight:700;margin-top:auto;font-variant-numeric:tabular-nums}
.pf-wrap .ctr-tileunit{font-size:11.5px;color:var(--text-3)}

.pf-wrap .ctr-free{display:grid;grid-template-columns:minmax(0,1fr) 130px auto;gap:8px}
@media (max-width:560px){.pf-wrap .ctr-free{grid-template-columns:minmax(0,1fr)}}
.pf-wrap .ctr-freehint{font-size:12px;color:var(--text-3);margin-top:6px}

.pf-wrap .ctr-empty{background:var(--surface);border:1px dashed var(--line-2);border-radius:16px;
  padding:22px;margin-bottom:18px}
.pf-wrap .ctr-emptytitle{font-weight:700;font-size:15px;margin-bottom:6px}
.pf-wrap .ctr-emptytext{color:var(--text-2);font-size:13.5px;max-width:56ch}

/* --- the sale panel --- */
/* Sticks below the pinned chrome — nav, plus the press alert bar when one is up. */
.pf-wrap .ctr-sale{position:sticky;top:calc(var(--pf-chrome-h) + 14px);
  background:var(--surface);border:1px solid var(--line);
  border-radius:18px;box-shadow:var(--shadow);padding:16px}
.pf-wrap .ctr-label{display:block;font-size:11.5px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--text-3);margin-bottom:5px}
.pf-wrap .ctr-cust{margin-bottom:12px}
.pf-wrap .ctr-newcust{background:none;border:0;padding:4px 0 0;font-size:12.5px;font-weight:600;
  color:var(--brand);cursor:pointer;font-family:inherit}

/* Customer typeahead. The list overlays the cart rather than pushing it down — a till whose totals
   jump around while you search is one whose operator stops trusting the number at the bottom. */
.pf-wrap .ctr-pick{position:relative}
.pf-wrap .ctr-pick .form-control{padding-right:34px}
.pf-wrap .ctr-pickclear{position:absolute;top:50%;right:6px;transform:translateY(-50%);
  width:26px;height:26px;display:grid;place-items:center;border:0;border-radius:7px;
  background:none;color:var(--text-3);font-size:17px;line-height:1;cursor:pointer}
.pf-wrap .ctr-pickclear:hover{background:var(--surface-2);color:var(--text)}
.pf-wrap .ctr-picklist{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:20;
  margin:0;padding:5px;list-style:none;max-height:266px;overflow-y:auto;
  background:var(--surface);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow)}
.pf-wrap .ctr-pickitem{display:flex;flex-direction:column;gap:1px;padding:7px 9px;border-radius:8px;
  cursor:pointer}
.pf-wrap .ctr-pickitem:hover,.pf-wrap .ctr-pickitem.is-active{background:var(--surface-2)}
.pf-wrap .ctr-pickname{font-size:13.5px;font-weight:600;color:var(--text)}
.pf-wrap .ctr-pickdetail{font-size:12px;color:var(--text-3)}
.pf-wrap .ctr-picknone{padding:9px;font-size:13px;color:var(--text-3);text-align:center}

.pf-wrap .ctr-cart{border-top:1px solid var(--line);border-bottom:1px solid var(--line);
  padding:6px 0;margin-bottom:12px;max-height:42vh;overflow-y:auto}
.pf-wrap .ctr-cartempty{color:var(--text-3);font-size:13px;text-align:center;padding:16px 0}
.pf-wrap .ctr-cartline{display:grid;grid-template-columns:minmax(0,1fr) auto auto auto;
  align-items:center;gap:8px;padding:7px 0}
.pf-wrap .ctr-cartname{font-size:13.5px;line-height:1.3;overflow-wrap:anywhere}
.pf-wrap .ctr-cartqty{display:flex;align-items:center;gap:2px}
.pf-wrap .ctr-cartstep{width:26px;height:26px;border:1px solid var(--line-2);border-radius:8px;
  background:var(--surface-2);color:var(--text-2);font-size:15px;line-height:1;cursor:pointer;
  font-family:inherit;padding:0}
.pf-wrap .ctr-cartstep:hover{border-color:var(--brand);color:var(--brand)}
.pf-wrap .ctr-cartcount{min-width:30px;text-align:center;font-size:13.5px;font-weight:600;
  font-variant-numeric:tabular-nums}
.pf-wrap .ctr-cartamount{min-width:62px;text-align:right;font-size:13.5px;font-weight:600;
  font-variant-numeric:tabular-nums}
.pf-wrap .ctr-cartkill{background:none;border:0;color:var(--text-3);font-size:17px;line-height:1;
  cursor:pointer;padding:0 2px;font-family:inherit}
.pf-wrap .ctr-cartkill:hover{color:var(--danger)}

.pf-wrap .ctr-totrow{display:flex;justify-content:space-between;font-size:13px;color:var(--text-2);
  padding:2px 0;font-variant-numeric:tabular-nums}
.pf-wrap .ctr-grand{font-size:22px;font-weight:700;color:var(--text);margin-top:6px;
  padding-top:8px;border-top:1px solid var(--line)}

.pf-wrap .ctr-tender{margin-top:14px}
.pf-wrap .ctr-methods{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}
.pf-wrap .ctr-method{display:flex;align-items:center;justify-content:center;gap:6px;
  border:1px solid var(--line-2);border-radius:10px;padding:9px 6px;font-size:13px;font-weight:600;
  cursor:pointer;transition:.12s}
.pf-wrap .ctr-method:hover{border-color:var(--brand)}
.pf-wrap .ctr-method:has(input:checked){border-color:var(--brand);background:var(--cyan-soft);
  color:var(--brand-deep)}
.pf-wrap .ctr-method input{accent-color:var(--brand)}

.pf-wrap .ctr-cash{margin-top:12px}
.pf-wrap .ctr-change{margin-top:8px;font-size:14px;color:var(--text-2);text-align:right}
.pf-wrap .ctr-change strong{font-size:20px;color:var(--text);font-variant-numeric:tabular-nums}

.pf-wrap .ctr-complete{display:flex;align-items:center;justify-content:space-between;gap:10px;
  width:100%;margin-top:16px;border:0;border-radius:14px;padding:15px 18px;background:var(--brand);
  color:#fff;font-family:inherit;font-size:16px;font-weight:700;cursor:pointer;transition:.12s;
  font-variant-numeric:tabular-nums}
.pf-wrap .ctr-complete:hover:not(:disabled){background:var(--brand-deep)}
.pf-wrap .ctr-complete:disabled{opacity:.45;cursor:not-allowed}
.pf-wrap .ctr-clear{display:block;width:100%;margin-top:8px;background:none;border:0;padding:6px;
  color:var(--text-3);font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}
.pf-wrap .ctr-clear:hover{color:var(--danger)}

/* ============ RUNNING TIMER (nav indicator) ============
   Leftmost in the actions cluster and present on every page, because the spec's whole reason for it is
   that a timer you can't see is a timer you forget. Deliberately a soft-warning chip rather than a
   danger one — a running timer is normal, it is only a *forgotten* one that costs anything. */
.pf-timer{display:inline-flex;align-items:center;gap:8px;background:var(--warn-soft);
  border:1px solid var(--warn);border-radius:999px;padding:3px 4px 3px 11px;margin-right:4px}
.pf-timer-job{font-size:12.5px;font-weight:700;color:var(--warn);text-decoration:none;
  font-variant-numeric:tabular-nums;white-space:nowrap}
.pf-timer-job:hover{text-decoration:underline}
.pf-timer-stop{display:inline-flex;align-items:center;gap:5px;border:0;border-radius:999px;
  background:var(--warn);color:#fff;font-family:inherit;font-size:11.5px;font-weight:700;
  padding:3px 10px;cursor:pointer;line-height:1.5}
.pf-timer-stop:hover{filter:brightness(.93)}
/* The pulse says "this is running right now" without any script keeping a clock on screen. */
.pf-timer-dot{width:6px;height:6px;border-radius:50%;background:#fff;animation:pf-timer-pulse 2s infinite}
@keyframes pf-timer-pulse{0%,100%{opacity:1}50%{opacity:.35}}
@media (prefers-reduced-motion:reduce){.pf-timer-dot{animation:none}}
/* On a narrow screen the job number goes and the Stop button stays — that is the actionable half. The
   shell block above shrinks the button itself to its dot at the same breakpoint. */
@media (max-width:900px){.pf-timer-job{display:none}}

/* ---- Press alert bar --------------------------------------------------------
   Pinned directly under the nav, as part of the chrome rather than as the top of the
   page: a press that jams while somebody is halfway down a job list is exactly the
   case the bar exists for, and one that scrolls away is one they never see. It is a
   sticky sibling at --pf-nav-h rather than a child of the nav, because the nav is a
   fixed-height flex row and its backdrop-filter makes it the containing block for the
   mobile drawer — both of which a second row inside it would fight. z-index sits one
   below the nav's so the drawer and its scrim still cover the bar when open.

   Slim by design: it is a standing condition, not a dialogue, and it appears on every
   page for anyone who opted in.

   Colour carries the severity, but the DOT carries the distinction that matters
   most: filled means we know what the press is doing, hollow means we cannot see
   it. A confirmed jam and a dead agent are different problems for different
   people, and the bar should say which at a glance rather than only in words. */
.pf-pressbar{
  position:sticky;top:var(--pf-nav-h);z-index:1029;
  display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;
  padding:.42rem clamp(.9rem,3vw,1.6rem);
  font-size:.82rem;line-height:1.35;
  border-bottom:1px solid var(--line-2);
  color:var(--text-2);
  /* Two layers now that page content passes underneath: the state tint on top — the same translucent
     token as before, so the colour is unchanged — over the nav's own frosted surface, which is what
     stops a line of text reading through the bar. The default tint covers nothing: every state that
     reaches the bar sets its own below. */
  --pf-pressbar-tint:transparent;
  background:linear-gradient(var(--pf-pressbar-tint),var(--pf-pressbar-tint)),
             color-mix(in srgb,var(--surface) 82%,transparent);
  backdrop-filter:saturate(180%) blur(12px);
  -webkit-backdrop-filter:saturate(180%) blur(12px)}
.pf-pressbar-dot{
  width:.5rem;height:.5rem;border-radius:50%;flex:0 0 auto;
  background:currentColor}
.pf-pressbar-name{font-weight:600;color:var(--text)}
.pf-pressbar-state{font-weight:600}
.pf-pressbar-detail{color:var(--text-2);
  /* The Fiery's own sentence can be long; never let it push the link off the row. */
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:min(46ch,42vw)}
.pf-pressbar-since,.pf-pressbar-more{color:var(--text-3);font-size:.76rem}
.pf-pressbar-link{margin-left:auto;color:inherit;font-weight:600;text-decoration:none;
  border-bottom:1px solid currentColor;padding-bottom:1px}
.pf-pressbar-link:hover{color:inherit;opacity:.75}
/* Dismiss. Last on the row and quieter than the link beside it: on any given day the useful action is
   to go and look at the press, not to put the message away. Sized for a finger anyway — the bar is a
   fixed height, so the hit area is grown with negative margin rather than padding, which would push the
   whole chrome down. */
.pf-pressbar-x{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:1.55rem;height:1.55rem;margin:-.3rem -.25rem -.3rem 0;padding:0;
  border:0;border-radius:50%;background:transparent;color:inherit;opacity:.6;cursor:pointer}
.pf-pressbar-x svg{width:.95rem;height:.95rem;fill:none;stroke:currentColor;stroke-width:2.2;
  stroke-linecap:round}
.pf-pressbar-x:hover{opacity:1;background:color-mix(in srgb,currentColor 14%,transparent)}
/* No hover on a touch screen, so the affordance has to be there without one. */
@media(hover:none){.pf-pressbar-x{opacity:.85}}
.pf-pressbar-x:disabled{opacity:.3;cursor:default}

/* Confirmed fault - the press told us something is wrong. */
.pf-pressbar-error{--pf-pressbar-tint:var(--danger-soft);color:var(--danger);border-bottom-color:var(--danger-soft)}
/* A fault is the one state that moves, and only a fault: a low cartridge stands for days, and a bar that
   moved for that would have taught everybody to ignore movement by the time the press actually stopped.

   Two things move, neither of them the bar. The dot sends out a ring on the same two-second beat the wall
   board's red card breathes at, and a band of light drifts through the tint from left to right, trailing a
   brighter line along the bottom hairline - the flow line off the dashboard, in red. It crosses and then
   rests, so the bar is still for as long as it is moving: a sweep that never stopped would be a loading
   shimmer, and somebody has to be able to type a quote underneath this.

   The band is a layer of its own, slid with a transform, so the browser moves a finished picture rather
   than repainting a blurred bar sixty times a second. It sits behind the text (the bar is already a
   stacking context) and is clipped by the bar, which costs nothing: the dismiss button's oversized hit
   area and the dot's ring both live inside the padding.

   Still is the base state, as on the wall: the reduced-motion rule kills every animation on the site,
   which leaves the band parked off the left edge and the dot wearing a steady halo no other state has. */
.pf-pressbar-error{overflow:hidden}
.pf-pressbar-error::before{
  content:"";position:absolute;z-index:-1;top:0;bottom:0;left:0;width:42%;pointer-events:none;
  background:
    linear-gradient(90deg,transparent,color-mix(in srgb,var(--danger) 75%,transparent) 50%,transparent)
      bottom/100% 1.5px no-repeat,
    linear-gradient(90deg,transparent,color-mix(in srgb,var(--danger) 14%,transparent) 50%,transparent);
  transform:translateX(-100%);
  animation:pf-pressbar-flow 7s cubic-bezier(.37,.12,.5,.88) .4s infinite}
/* 42% wide, so 240% of itself is one whole bar: off the left edge to off the right. */
@keyframes pf-pressbar-flow{
  0%{transform:translateX(-100%)}
  64%,100%{transform:translateX(240%)}}
.pf-pressbar-error .pf-pressbar-dot{position:relative;
  box-shadow:0 0 0 3px color-mix(in srgb,currentColor 20%,transparent)}
.pf-pressbar-error .pf-pressbar-dot::after{
  content:"";position:absolute;inset:0;border-radius:50%;background:currentColor;opacity:0;
  animation:pf-pressbar-ping 2s cubic-bezier(0,0,.2,1) infinite}
@keyframes pf-pressbar-ping{
  0%{transform:scale(1);opacity:.5}
  75%,100%{transform:scale(3.2);opacity:0}}
/* Running, but wants attention before it becomes a stoppage. */
.pf-pressbar-warning,
.pf-pressbar-offline{--pf-pressbar-tint:var(--warn-soft);color:var(--warn);border-bottom-color:var(--warn-soft)}
/* The two blind states. Deliberately quieter than a fault and hollow-dotted: nothing
   is known to be wrong, which is precisely the problem. */
.pf-pressbar-unreachable,
.pf-pressbar-stale{--pf-pressbar-tint:var(--chip);color:var(--text-2)}
.pf-pressbar-unreachable .pf-pressbar-dot,
.pf-pressbar-stale .pf-pressbar-dot{
  background:transparent;box-shadow:inset 0 0 0 1.5px currentColor}

@media (max-width:640px){
  /* On a phone the press name and state are the message; the rest is detail. The dismiss button stays:
     a bar that cannot be put away is worst on the smallest screen, where it costs the most of it. */
  .pf-pressbar-detail,.pf-pressbar-since{display:none}
  .pf-pressbar{font-size:.78rem}
}

/* ============ SIGNATURES ============
   A name a customer signed a proof approval with online, set the way they saw it on the proof page. */
.pf-signature{font-family:"Segoe Script","Snell Roundhand","Brush Script MT","Apple Chancery",cursive;
  font-size:1.05rem;line-height:1.25}

/* ============ HELP CENTRE ============================================================
   /help, /help/search and the articles. Reading surfaces, not working surfaces: a measured
   column, generous leading, and a sidebar that carries position rather than controls.
   ==================================================================================== */

.pf-help-hero{padding:26px 0 22px;max-width:760px}
.pf-help-hero.tight{padding:6px 0 14px}
.pf-help-hero h1{font-size:30px;font-weight:650;letter-spacing:-.025em;margin:0 0 6px}
.pf-help-hero>p{color:var(--text-2);font-size:14.5px;margin:0 0 16px}

/* Search. One control on the index, the same control shrunk in the article sidebar. */
.pf-help-search{display:flex;align-items:center;gap:10px;background:var(--surface);
  border:1px solid var(--line-2);border-radius:14px;padding:6px 8px 6px 14px;
  box-shadow:var(--shadow-sm)}
.pf-help-search:focus-within{border-color:var(--brand);box-shadow:0 0 0 3px var(--cyan-soft)}
.pf-help-search .pf-icon{width:18px;height:18px;color:var(--text-3);flex:none}
.pf-help-search input{flex:1;min-width:0;border:0;background:none;outline:none;color:var(--text);
  font-size:15px;padding:8px 0}
.pf-help-search input::placeholder{color:var(--text-3)}
.pf-help-search.sm{border-radius:11px;padding:3px 6px 3px 11px;box-shadow:none;background:var(--surface-2)}
.pf-help-search.sm input{font-size:13.5px;padding:6px 0}

.pf-help-count{color:var(--text-2);font-size:13.5px;margin:14px 0 0}
/* Results sit in the same column as the box that produced them; a full-width card under a 760px
   search field reads as two unrelated things. */
.pf-help-results{max-width:760px}
.pf-help-nores{color:var(--text-2);font-size:14px;margin:0 0 18px}

/* Index: sections packed into columns rather than a grid. A grid gives every card in a row the height
   of the tallest one, and with two-article sections sitting beside six-article ones that is mostly
   whitespace. Columns pack vertically instead, so the page is only as tall as its content. */
.pf-help-sections{column-count:3;column-gap:18px}
.pf-help-section{break-inside:avoid;width:100%;margin:0 0 18px;display:inline-block}
@media(max-width:1180px){.pf-help-sections{column-count:2}}

.pf-help-section .pf-card-h{padding:16px 20px 10px}
.pf-help-list{padding:0 10px 10px}
.pf-help-item{display:block;padding:9px 10px;border-radius:11px;text-decoration:none;
  transition:background .12s ease}
.pf-help-item:hover{background:var(--surface-2)}
.pf-help-item .t{display:block;font-size:14px;font-weight:600;color:var(--text);
  letter-spacing:-.01em}
.pf-help-item .t .sec{font-weight:500;color:var(--text-3);font-size:12px;margin-left:8px}
.pf-help-item .s{display:block;font-size:12.5px;color:var(--text-2);line-height:1.45;margin-top:1px}

.pf-help-foot{color:var(--text-3);font-size:12.5px;margin:22px 0 0;max-width:680px}

/* ---- Article ---- */
.pf-help-doc{display:grid;grid-template-columns:230px minmax(0,1fr);gap:26px;align-items:start;
  padding-top:6px}
/* Clears the pinned chrome — nav, plus the press alert bar when one is up. */
.pf-help-aside{position:sticky;top:calc(var(--pf-chrome-h) + 14px);display:flex;flex-direction:column;gap:16px}

.pf-help-nav{display:flex;flex-direction:column;gap:1px}
.pf-help-nav .lbl{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--text-3);padding:0 10px 6px}
.pf-help-nav a{font-size:13.2px;color:var(--text-2);text-decoration:none;padding:6px 10px;
  border-radius:9px;line-height:1.35}
.pf-help-nav a:hover{background:var(--surface-2);color:var(--text)}
/* The article you are reading. A left rule rather than a fill, so the sidebar stays quiet. */
.pf-help-nav a.on{color:var(--brand);font-weight:600;background:var(--cyan-soft)}

.pf-help-all{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;font-weight:600;
  color:var(--text-3);text-decoration:none;padding:0 10px}
.pf-help-all:hover{color:var(--brand)}
.pf-help-all .pf-icon{width:15px;height:15px}

.pf-help-body{padding:30px 38px 34px;max-width:820px}
.pf-help-crumb{display:flex;gap:8px;font-size:12px;color:var(--text-3);margin-bottom:10px}
.pf-help-crumb a{color:var(--text-3);text-decoration:none}
.pf-help-crumb a:hover{color:var(--brand)}
.pf-help-body h1{font-size:27px;font-weight:650;letter-spacing:-.025em;margin:0 0 8px}
.pf-help-summary{font-size:15.5px;color:var(--text-2);line-height:1.55;margin:0 0 4px}

/* ---- Prose. Everything below here styles Markdown output, so selectors are bare tags. ---- */
.pf-prose{font-size:15px;line-height:1.68;color:var(--text);padding-top:14px}
.pf-prose>*:first-child{margin-top:0}
.pf-prose p{margin:0 0 15px}
.pf-prose h2{font-size:19px;font-weight:650;letter-spacing:-.015em;margin:32px 0 12px;
  padding-top:14px;border-top:1px solid var(--line)}
.pf-prose h2:first-child{margin-top:0;padding-top:0;border-top:0}
.pf-prose h3{font-size:15.5px;font-weight:650;margin:22px 0 8px}
.pf-prose ul,.pf-prose ol{margin:0 0 15px;padding-left:22px}
.pf-prose li{margin:0 0 6px}
.pf-prose li>ul,.pf-prose li>ol{margin:6px 0 0}
.pf-prose a{color:var(--brand);text-decoration:none;border-bottom:1px solid transparent}
.pf-prose a:hover{border-bottom-color:currentColor}
.pf-prose strong{font-weight:650}
.pf-prose code{font-size:.88em;background:var(--chip);padding:1.5px 5px;border-radius:5px;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.pf-prose pre{background:var(--surface-2);border:1px solid var(--line);border-radius:11px;
  padding:13px 15px;overflow-x:auto;margin:0 0 15px}
.pf-prose pre code{background:none;padding:0;font-size:12.6px;line-height:1.6}
.pf-prose hr{border:0;border-top:1px solid var(--line);margin:26px 0}

/* A blockquote is the one flourish the articles use: "watch out for this". */
.pf-prose blockquote{margin:0 0 15px;padding:12px 16px;background:var(--warn-soft);
  border-left:3px solid var(--warn);border-radius:0 11px 11px 0;color:var(--text)}
.pf-prose blockquote p:last-child{margin-bottom:0}

/* Tables scroll inside themselves; several articles carry a wide reference grid and the page
   body must never scroll sideways. */
.pf-prose table{width:100%;border-collapse:collapse;margin:0 0 15px;font-size:13.6px;display:block;
  overflow-x:auto}
.pf-prose th,.pf-prose td{border-bottom:1px solid var(--line);padding:8px 12px;text-align:left;
  vertical-align:top}
.pf-prose th{font-weight:650;color:var(--text-2);font-size:12px;letter-spacing:.03em;
  text-transform:uppercase;white-space:nowrap}

.pf-help-related{margin-top:30px;padding-top:18px;border-top:1px solid var(--line);
  display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:10px}
.pf-help-related .lbl{grid-column:1/-1;font-size:11px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-3)}
.pf-help-related a{display:block;padding:11px 14px;border:1px solid var(--line);border-radius:12px;
  text-decoration:none;transition:border-color .12s ease,background .12s ease}
.pf-help-related a:hover{border-color:var(--brand);background:var(--cyan-soft)}
.pf-help-related .t{display:block;font-size:13.5px;font-weight:600;color:var(--text)}
.pf-help-related .s{display:block;font-size:12.2px;color:var(--text-2);line-height:1.45;margin-top:2px}

@media(max-width:900px){
  /* The sidebar goes under the article rather than over it. Above it, six sibling links are a screen
     of navigation before the first sentence of the thing you actually opened — and someone arriving
     from the "?" button is already on the article they wanted. The on-page contents list goes
     entirely: on a phone it is a second scroll of the thing you are already scrolling. */
  .pf-help-doc{grid-template-columns:minmax(0,1fr);gap:16px}
  .pf-help-aside{position:static;order:2}
  .pf-help-body{order:1}
  .pf-help-aside .pf-help-nav:nth-of-type(2){display:none}
  .pf-help-body{padding:22px 20px 26px}
  .pf-help-hero h1{font-size:25px}
  .pf-help-sections{column-count:1}
}

/* Job pill: an order line's job, as its number and where the job is. Coloured by the status's lifecycle
   stage rather than by the status itself, because statuses are the shop's own to rename and add to and a
   colour per name would have to be configured; the stage is fixed and says enough at a glance. A hold
   overrides the stage — it is the one thing on the line somebody has to act on. */
.pf-jobpill{display:inline-flex;align-items:center;gap:7px;max-width:100%;padding:3px 10px 3px 9px;
  border-radius:999px;border:1px solid transparent;background:var(--chip);color:var(--text-2);
  font-size:12.5px;line-height:1.35;text-decoration:none;white-space:nowrap;transition:filter .12s ease}
.pf-jobpill:hover{filter:brightness(.96);color:var(--text)}
.pf-jobpill b{font-weight:700;color:var(--text);font-variant-numeric:tabular-nums}
.pf-jobpill .st{display:inline-flex;align-items:center;gap:5px;min-width:0;overflow:hidden;text-overflow:ellipsis}
.pf-jobpill .st::before{content:"";flex:none;width:7px;height:7px;border-radius:50%;background:currentColor;opacity:.85}
.pf-jobpill .qty{color:var(--text-3);font-variant-numeric:tabular-nums}
.pf-jobpill.stage-artwork{background:var(--cyan-soft);color:var(--brand-deep)}
.pf-jobpill.stage-production{background:var(--warn-soft);color:var(--warn)}
.pf-jobpill.stage-dispatch{background:var(--ok-soft);color:var(--ok)}
.pf-jobpill.stage-cancelled{background:var(--chip);color:var(--text-3)}
.pf-jobpill.stage-cancelled b{text-decoration:line-through;color:var(--text-3)}
.pf-jobpill.is-held{background:var(--danger-soft);color:var(--danger);border-color:var(--danger)}
/* An order line's job under its description: the pill, then department and due date dotted apart. */
.pf-linejob{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;margin-top:6px;font-size:12.5px;color:var(--text-3)}
.pf-linejob>span+span::before{content:"·";margin-right:8px}

/* Create-job screen: what the job is, one line of spec in muted text with its parts dotted apart. */
.pf-newjob-spec span+span::before{content:" · "}
.pf-newjob .card-header .pf-jobpill{flex:none}

/* Service & supplies (/machine-requests). The action people came for is one big tap target, sized for a phone
   held at the machine; the request lists are list-group rows whose parts wrap, so a row on a phone is already a
   card and needs none of pflist's table-to-card machinery. */
.pf-mr-action{display:flex;align-items:center;gap:16px;padding:18px 20px;border-radius:var(--bs-border-radius-lg);
  background:var(--surface);border:1px solid var(--line);box-shadow:var(--shadow-sm);color:var(--text);
  text-decoration:none;transition:border-color .12s ease,box-shadow .12s ease}
.pf-mr-action:hover{border-color:var(--brand);box-shadow:var(--shadow);color:var(--text)}
.pf-mr-action svg{flex:none;width:44px;height:44px;padding:10px;border-radius:12px;background:var(--cyan-soft);
  fill:none;stroke:var(--brand-deep);stroke-width:1.8;stroke-linejoin:round}
.pf-mr-action-title{display:block;font-weight:700;font-size:17px}
.pf-mr-action-sub{display:block;color:var(--text-2);font-size:13.5px}
.pf-mr-row{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px}
/* The machine's details under the picker, and the press report beside its tick box. */
.pf-mr-machine{padding:10px 14px;border-radius:var(--bs-border-radius);background:var(--surface-2);font-size:14px}
.pf-mr-machine>div+div{margin-top:2px}
.pf-mr-press{padding:10px 14px;border-radius:var(--bs-border-radius);background:var(--warn-soft);font-size:14px}
.pf-mr-press.is-quiet{background:var(--surface-2)}
/* A second link on the press bar sits beside the first rather than splitting the free space with it. */
.pf-pressbar-link+.pf-pressbar-link{margin-left:.9rem}

/* ===== Stock inventory (V176) =====
   The stock page's three figures: on hand, committed, available. Available carries the state: amber at or
   under the reorder point, red when jobs are committed to more than is on the shelf. */
.pf-inv-figs{display:flex;flex-wrap:wrap;gap:12px 36px}
.pf-inv-fig{min-width:120px}
.pf-inv-k{font-size:12px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--text-3)}
.pf-inv-v{font-size:1.5rem;font-weight:600;font-variant-numeric:tabular-nums;line-height:1.3}
.pf-inv-sub{font-size:12.5px;color:var(--text-2)}
.pf-inv-fig.low .pf-inv-v{color:var(--bs-warning-text-emphasis)}
.pf-inv-fig.short .pf-inv-v,.pf-inv-fig.short .pf-inv-sub{color:var(--bs-danger-text-emphasis)}
.pf-inv-settings dt{font-weight:500;color:var(--text-2)}
.pf-inv-ledger td{vertical-align:top}

/* A consumable's colour dot: a toner or ink reads as its colour faster than as its name. Outlined, so white and
   clear still show as something. */
.pf-swatch{flex:none;display:inline-block;width:14px;height:14px;border-radius:50%;
  border:1px solid rgba(6,38,55,.25);box-shadow:inset 0 0 0 1px rgba(255,255,255,.35)}

/* The two things this screen is for, side by side on a desktop and stacked on a phone. */
.pf-mr-actions{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(min(100%,17rem),1fr))}

/* Usage over the last 12 months, under the supplies order form. A row per consumable: what it is, a sparkline of
   what was ordered each month, and the same thing in figures — the numbers are the readable version, so no value
   is ever printed on a bar. */
.pf-usage-row{display:grid;grid-template-columns:minmax(7rem,12rem) 120px auto 1fr;align-items:center;
  gap:6px 14px;padding:7px 0;border-top:1px solid var(--line)}
.pf-usage-row:first-child{border-top:0}
.pf-usage-name{display:flex;align-items:center;gap:7px;font-size:14px;min-width:0}
.pf-usage-name>span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* One series, so one hue: the brand's. Bars sit on the baseline and keep a 2px gap of surface between them. */
.pf-spark{display:block;width:120px;height:28px;color:var(--brand)}
.pf-usage-figures{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pf-spark-base{fill:currentColor;opacity:.18}
.pf-usage-suggestion{color:var(--ok);font-weight:600;white-space:nowrap;text-align:right;
  overflow:hidden;text-overflow:ellipsis}
@media (max-width:640px){
  /* On a phone the row stacks: name, then the bars beside the figures. */
  .pf-usage-row{grid-template-columns:120px 1fr;gap:4px 12px}
  .pf-usage-name,.pf-usage-suggestion{grid-column:1 / -1}
  /* Narrow enough that trimming the figures would cut the rate off; they wrap instead. */
  .pf-usage-figures{white-space:normal;overflow:visible}
  .pf-usage-suggestion{text-align:left}
}

/* ============ JOB PAGE (jobs/view.html) ============
   Read first, edit on demand. A flow river (the dashboard's .pf-flow parts, six stations for one job), then a
   main column of collapsible sections in the order the work happens, and a right-hand column of what you glance
   at. Sections are <details>, so the page works with no script; jobview.js only remembers what you opened. */
.jv-sprite{position:absolute;width:0;height:0;overflow:hidden}
.jv-head{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap;margin-bottom:16px}
.jv-head-main{min-width:0}
.jv-crumbs{font-size:12.5px;color:var(--text-3);margin-bottom:4px}
.jv-crumbs a{color:var(--text-2);text-decoration:none}
.jv-crumbs a:hover{color:var(--brand)}
.jv-title{margin:0;font-size:26px;font-weight:700;letter-spacing:-.02em;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.jv-pill{display:inline-flex;align-items:center;gap:6px;padding:3px 11px;border-radius:999px;font-size:12.5px;font-weight:600;
  background:var(--cyan-soft);color:var(--brand-deep);letter-spacing:0}
.jv-pill::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
.jv-sub{margin-top:3px;color:var(--text-2);font-size:15px}
.jv-sub b{color:var(--text);font-weight:600}
.jv-sub a{color:var(--text-2);text-decoration:none}
.jv-sub a:hover{color:var(--brand)}
.jv-dot{margin:0 6px;color:var(--text-3)}
.jv-chips{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px}
.jv-chips:empty{display:none}
.jv-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;
  background:var(--chip);color:var(--text-2);text-decoration:none;white-space:nowrap}
.jv-chip .pf-icon{width:.95em;height:.95em}
.jv-chip.brand{background:var(--cyan-soft);color:var(--brand-deep)}
.jv-chip.ok{background:var(--ok-soft);color:var(--ok)}
.jv-chip.warn{background:var(--warn-soft);color:var(--warn)}
.jv-chip.danger{background:var(--danger-soft);color:var(--danger)}
.jv-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}

/* the flow: six stations, so their centres sit at the river's nodes, x = 100, 300 … 1100 of 1200 */
.jv-flow{padding:20px 20px 4px;margin-bottom:14px}
.jv-stations{grid-template-columns:repeat(6,minmax(0,1fr))}
.jv-station .c{font-size:19px;line-height:1.25;color:var(--text-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.jv-station .l,.jv-station .s{color:var(--text-3)}
.jv-station .s{min-height:1.2em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.jv-station.done .c{color:var(--text)}
.jv-station.done .l,.jv-station.done .s{color:var(--text-2)}
.jv-station.now .l,.jv-station.now .c{color:var(--brand)}
.jv-station.now .s{color:var(--text-2)}
.jv-station.skip .c{font-weight:400}
.jv-river .jv-dot{fill:var(--surface);stroke:var(--line-2);stroke-width:2.6}
.jv-river .jv-halo{fill:var(--brand);opacity:0}
.jv-river .done .jv-dot{fill:url(#pf-flowgrad);stroke:url(#pf-flowgrad)}
.jv-river .now .jv-dot{stroke:url(#pf-flowgrad)}
.jv-river .now .jv-halo{opacity:.16;animation:jv-halo 2.4s ease-in-out infinite;transform-origin:center;transform-box:fill-box}
.jv-river .skip .jv-dot{stroke-dasharray:3 3}
@keyframes jv-halo{50%{transform:scale(1.4);opacity:.05}}
@media (prefers-reduced-motion:reduce){.jv-river .now .jv-halo{animation:none}}

.jv-attention{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}
.jv-attention:not(:has(.jv-alert)){display:none}
.jv-alert{display:flex;gap:10px;align-items:flex-start;padding:10px 14px;border-radius:12px;background:var(--warn-soft);font-size:13.5px}
.jv-alert-ic{font-weight:700;color:var(--warn);width:14px;text-align:center;flex:none}
.jv-alert.info{background:var(--cyan-soft)} .jv-alert.info .jv-alert-ic{color:var(--brand)}
.jv-alert.danger{background:var(--danger-soft)} .jv-alert.danger .jv-alert-ic{color:var(--danger)}
.jv-alert.muted{background:var(--chip)} .jv-alert.muted .jv-alert-ic{color:var(--text-2)}

.jv-grid{display:grid;grid-template-columns:minmax(0,1fr) 330px;gap:22px;align-items:start}
.jv-main{min-width:0}
.jv-rail{display:flex;flex-direction:column;gap:14px;min-width:0}

/* sections */
.jv-sec{background:var(--surface);border:1px solid var(--line);border-radius:16px;margin-bottom:10px;box-shadow:var(--shadow-sm);
  scroll-margin-top:80px;transition:box-shadow .2s}
.jv-sec[open]{box-shadow:var(--shadow)}
.jv-sec>summary{list-style:none;display:flex;align-items:center;gap:12px;padding:13px 18px;cursor:pointer;border-radius:16px}
.jv-sec>summary::-webkit-details-marker{display:none}
.jv-sec>summary:hover{background:var(--surface-2)}
.jv-sec>summary:focus-visible{outline:2px solid var(--brand);outline-offset:-2px}
.jv-sec[open]>summary{border-bottom:1px solid var(--line);border-radius:16px 16px 0 0}
.jv-ic{width:30px;height:30px;border-radius:10px;display:grid;place-items:center;background:var(--chip);color:var(--text-2);flex:none}
.jv-ic svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2}
.jv-sec.is-done .jv-ic{background:var(--ok-soft);color:var(--ok)}
.jv-sec.is-now .jv-ic{background:var(--cyan-soft);color:var(--brand)}
.jv-t{font-weight:600;font-size:15px;white-space:nowrap}
.jv-s{color:var(--text-2);font-size:13px;margin-left:auto;text-align:right;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.jv-chev{width:16px;height:16px;color:var(--text-3);flex:none;transition:transform .2s}
.jv-sec[open] .jv-chev{transform:rotate(90deg)}
.jv-sec.quiet{background:transparent;box-shadow:none;border-style:dashed}
.jv-sec.quiet:not([open]) .jv-t{color:var(--text-2);font-weight:500}
.jv-sec.quiet[open]{background:var(--surface)}
.jv-body{padding:18px;position:relative}
.jv-body .table{--bs-table-bg:transparent}
.jv-body .table thead th{font-size:11.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-3);font-weight:600}
/* the shared activity / send-history fragments are cards; inside a section they lose their frame */
.jv-flat .card{border:0;box-shadow:none;background:transparent}
.jv-flat .card-header{padding-left:0;padding-right:0}
.jv-flat .list-group-item{background:transparent;padding-left:0;padding-right:0}

/* read view + edit on demand: the Edit summary sits top-right; opening it swaps the read view for the form */
.jv-edit>summary{list-style:none;position:absolute;top:14px;right:18px;z-index:1}
.jv-edit>summary::-webkit-details-marker{display:none}
.jv-edit[open]>summary{display:none}
.jv-edit[open]~.jv-read{display:none}
.jv-edit:not([open])~.jv-read{padding-right:110px}
.jv-formbar{display:flex;gap:8px;align-items:center;margin-top:16px}
.jv-lead{margin:0 0 12px;font-size:14px}
.jv-pre{white-space:pre-line}
.jv-spec{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border:1px solid var(--line);border-radius:12px;overflow:hidden}
.jv-spec>div{padding:9px 13px;border-right:1px solid var(--line);border-bottom:1px solid var(--line);min-width:0}
.jv-spec>div:nth-child(3n){border-right:0}
.jv-spec .wide{grid-column:1/-1;border-right:0}
.jv-spec .k{font-size:11px;color:var(--text-3);text-transform:uppercase;letter-spacing:.05em;font-weight:600}
.jv-spec .v{font-weight:500;margin-top:1px;overflow-wrap:anywhere}
.jv-note{background:var(--surface-2);border-radius:10px;padding:9px 13px;color:var(--text-2);margin-bottom:12px}
.jv-more{margin-top:6px}
.jv-more>summary{cursor:pointer;color:var(--brand);font-weight:600;font-size:13px;margin-bottom:10px;width:max-content}

/* proof rounds as a short story */
.jv-rounds{position:relative;padding-left:26px}
.jv-rounds::before{content:"";position:absolute;left:9px;top:8px;bottom:8px;width:2px;background:var(--line-2)}
.jv-round{position:relative;padding-bottom:16px}
.jv-round:last-child{padding-bottom:0}
.jv-round::before{content:"";position:absolute;left:-22px;top:5px;width:12px;height:12px;border-radius:50%;background:var(--surface);border:2px solid var(--text-3)}
.jv-round.rev::before{border-color:var(--warn);background:var(--warn)}
.jv-round.ok::before{border-color:var(--ok);background:var(--ok)}
.jv-round.cur::before{border-color:var(--brand);background:var(--brand);box-shadow:0 0 0 4px var(--cyan-soft)}
.jv-round-h{display:flex;gap:8px;align-items:baseline;flex-wrap:wrap}
.jv-round-h a{overflow-wrap:anywhere}
.jv-quote{margin-top:5px;padding:6px 10px;border-left:3px solid var(--warn);background:var(--warn-soft);border-radius:0 8px 8px 0;font-size:13px}
.jv-signed{margin-top:4px;font-size:13px}
.jv-current{margin-top:10px;border:1px solid var(--river);background:var(--cyan-soft);border-radius:14px;padding:14px}
.jv-current-h{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px}
.jv-filebar{display:flex;gap:8px;margin-bottom:10px}
.jv-filebar .form-control{background:var(--surface)}
.jv-filebar .btn{white-space:nowrap}
.jv-acts{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-start}
.jv-act>summary{list-style:none}
.jv-act>summary::-webkit-details-marker{display:none}
.jv-act[open]{flex-basis:100%}
.jv-act[open]>summary{margin-bottom:8px}
.jv-act-form{background:var(--surface);border-radius:10px;padding:10px 12px;max-width:520px}
.jv-skip{margin-top:10px;text-align:right}

/* files */
.jv-files{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;margin-bottom:14px}
.jv-file{border:1px solid var(--line);border-radius:12px;padding:9px 11px;display:flex;gap:10px;align-items:center;min-width:0}
.jv-ext{width:34px;height:40px;border-radius:7px;background:var(--chip);display:grid;place-items:center;font-size:10px;font-weight:700;color:var(--text-2);flex:none}
.jv-file-main{min-width:0;flex:1}
.jv-file-n{display:block;font-weight:500;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-decoration:none}
.jv-file-m{font-size:11.5px;color:var(--text-3)}
.jv-file-x{flex:none}
.jv-upload{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr) auto;gap:8px}

.jv-money{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-bottom:14px;max-width:460px}
.jv-money>div{background:var(--surface-2);border-radius:12px;padding:10px 14px}
.jv-money .k{font-size:12px;color:var(--text-3)}
.jv-money .v{font-size:19px;font-weight:600;font-variant-numeric:tabular-nums}

/* the right-hand column */
.jv-card{background:var(--surface);border:1px solid var(--line);border-radius:16px;padding:16px;box-shadow:var(--shadow-sm)}
.jv-card h3{display:flex;justify-content:space-between;align-items:center;margin:0 0 10px;font-size:11.5px;text-transform:uppercase;
  letter-spacing:.07em;color:var(--text-3);font-weight:700}
.jv-card h3 a{font-size:12.5px;letter-spacing:0;text-transform:none;font-weight:600;text-decoration:none}
.jv-kv{display:flex;justify-content:space-between;gap:12px;padding:6px 0;border-top:1px solid var(--line);font-size:13px}
.jv-qty+.jv-kv,.jv-due+.jv-kv,.jv-card h3+.jv-kv{border-top:0}
.jv-kv .k{color:var(--text-2);flex:none}
.jv-kv .v{font-weight:500;text-align:right;min-width:0;overflow-wrap:anywhere}
.jv-kv .v .pf-hang{display:block}
.jv-kv .v.jv-prov{font-style:italic;color:var(--text-2);font-weight:400}
.jv-qty{display:flex;align-items:baseline;gap:8px;margin-bottom:6px}
.jv-qty b{font-size:30px;font-weight:300;letter-spacing:-.02em;line-height:1;font-variant-numeric:tabular-nums}
.jv-jobnotes{margin-top:10px;background:var(--warn-soft);border-radius:10px;padding:8px 11px;font-size:12.5px}
.jv-jobnotes b{display:block;font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--warn);margin-bottom:1px}
.jv-due{display:flex;align-items:baseline;flex-wrap:wrap;gap:8px;margin-bottom:8px}
.jv-due .big{font-size:22px;font-weight:700;letter-spacing:-.02em}
.jv-person{display:flex;align-items:center;gap:10px;padding:5px 0}
.jv-person .n{font-size:13px;font-weight:500}
.jv-person .r{font-size:11.5px;color:var(--text-3)}
.jv-av{width:28px;height:28px;border-radius:50%;display:grid;place-items:center;font-size:10.5px;font-weight:700;color:#fff;flex:none;background:var(--text-3)}
.jv-av.rep{background:#0097dc} .jv-av.design{background:#8b5cf6} .jv-av.lead{background:#12a17a} .jv-av.customer{background:#df8500}
.jv-timer{display:flex;align-items:baseline;gap:8px;margin-bottom:10px}
.jv-timer .big{font-size:22px;font-weight:700;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.jv-start{display:flex;gap:8px}
.jv-running{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.jv-offer{display:flex;flex-wrap:wrap;align-items:center;gap:6px;background:var(--cyan-soft);border-radius:10px;padding:8px 10px;margin-bottom:10px;font-size:13px}
.jv-offer>span{flex-basis:100%}

@media (max-width:1100px){
  .jv-grid{grid-template-columns:minmax(0,1fr)}
  .jv-rail{order:-1;display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:760px){
  /* As on the dashboard, the river goes and the stations become tiles; six of them, so none spans the row. */
  .jv-stations{grid-template-columns:repeat(3,minmax(0,1fr))}
  .jv-stations .pf-station:last-child{grid-column:auto}
  .jv-station .c{font-size:16px}
  .jv-station.now{border-color:var(--brand)}
  .jv-rail{grid-template-columns:minmax(0,1fr)}
  .jv-spec{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
  .jv-spec>div:nth-child(3n){border-right:1px solid var(--line)}
  .jv-spec>div:nth-child(2n){border-right:0}
  .jv-upload{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
  .jv-upload input[type=file]{grid-column:1/-1}
  .jv-s{display:none}
  .jv-edit:not([open])~.jv-read{padding-right:0;padding-top:34px}
  .jv-title{font-size:22px}
}

/* "Time on this round" (V184): in the proof email dialog and the job page's Mark sent form. */
.pf-roundtime{border-top:1px solid var(--line);padding-top:12px}
.pf-roundtime-h{font-size:13px;font-weight:600;margin-bottom:6px}
.pf-roundtime-row{display:grid;grid-template-columns:minmax(0,1fr) 4.5rem 4.5rem;gap:6px}
.jv-act-form .pf-roundtime{margin-top:12px}
.jv-act-form .btn{white-space:nowrap}

/* ── Preflight (V187): the proof file checked against the job, under Artwork & proof ─────────────────── */
.pf-pre{background:var(--surface);border:1px solid var(--line);border-left:4px solid var(--ok);border-radius:12px;padding:10px 12px;margin-bottom:10px}
.pf-pre.is-warn{border-left-color:var(--warn)}
.pf-pre.is-bad{border-left-color:var(--danger)}
.pf-pre.is-sent{border-left-color:var(--text-3)}
.pf-pre-h{display:flex;align-items:flex-start;gap:10px}
.pf-pre-mark{flex:none;width:22px;height:22px;border-radius:50%;display:grid;place-items:center;font-size:12px;font-weight:700;background:var(--ok-soft);color:var(--ok)}
.pf-pre.is-warn .pf-pre-mark{background:var(--warn-soft);color:var(--warn)}
.pf-pre.is-bad .pf-pre-mark{background:var(--danger-soft);color:var(--danger)}
.pf-pre.is-sent .pf-pre-mark{background:var(--surface-2);color:var(--text-2)}
.pf-pre-t{min-width:0;display:flex;flex-direction:column;line-height:1.3}
.pf-pre-t strong{font-size:14px}
.pf-pre-spec{font-size:12px;color:var(--text-3)}
.pf-pre-stale{margin-top:8px;padding:6px 10px;border-radius:8px;background:var(--warn-soft);color:var(--text-2)}
.pf-pre-body{display:flex;gap:14px;margin-top:10px;align-items:flex-start}
.pf-pre-thumb{flex:none;width:200px;max-width:40%;display:flex;flex-direction:column;gap:4px;text-decoration:none}
.pf-pre-thumb img{width:100%;height:auto;border-radius:6px;border:1px solid var(--line);background:#fff}
.pf-pre-key{font-size:11px;color:var(--text-3);display:flex;align-items:center;gap:5px;flex-wrap:wrap}
.pf-pre-key i{display:inline-block;width:14px;height:0;border-top:2px solid #e6007e}
.pf-pre-key i.k-safe{border-top:2px dashed #0096dc;margin-left:6px}
.pf-pre-list{min-width:0;flex:1;display:flex;flex-direction:column;gap:8px}
.pf-pre-f{display:flex;gap:8px;align-items:flex-start}
.pf-pre-ic{flex:none;width:18px;height:18px;border-radius:50%;display:grid;place-items:center;font-size:10.5px;font-weight:700;margin-top:1px;background:var(--surface-2);color:var(--text-2)}
.pf-pre-f.is-fail .pf-pre-ic{background:var(--danger-soft);color:var(--danger)}
.pf-pre-f.is-warn .pf-pre-ic{background:var(--warn-soft);color:var(--warn)}
.pf-pre-f.is-info .pf-pre-ic{background:var(--cyan-soft);color:var(--brand-deep);font-style:italic;font-family:Georgia,serif}
.pf-pre-ft{font-size:13.5px;font-weight:600;overflow-wrap:anywhere}
.pf-pre-f.is-info .pf-pre-ft{font-weight:500}
.pf-pre-fd{font-size:12.5px;color:var(--text-2);overflow-wrap:anywhere}
.pf-pre-passed{display:flex;flex-wrap:wrap;gap:4px 12px;font-size:12px;color:var(--ok)}
.pf-pre-passed:empty{display:none}
.pf-pre-over{margin-top:10px;padding-top:8px;border-top:1px solid var(--line);color:var(--text-2)}
.pf-pre-anyway{margin-top:0}
.pf-pre-actions{margin-top:10px}
.pf-pre-cust{margin-top:8px;padding:6px 10px;border-radius:8px;background:var(--cyan-soft);color:var(--text-2)}
.pf-pre-custnote{margin-top:4px;font-style:italic;color:var(--text)}
.pf-pre-chip{display:inline-flex;align-items:center;padding:1px 8px;border-radius:999px;font-size:11.5px;font-weight:600;background:var(--ok-soft);color:var(--ok)}
.pf-pre-chip.is-warn{background:var(--warn-soft);color:var(--warn)}
.pf-pre-chip.is-bad{background:var(--danger-soft);color:var(--danger)}
.pf-pre-chip.is-sent{background:var(--surface-2);color:var(--text-2)}
@media (max-width:640px){
  .pf-pre-body{flex-direction:column}
  .pf-pre-thumb{width:100%;max-width:320px}
}

/* ============ CHAT (static/js/chat.js; docs/internal-messaging-spec.md §8) ============
   One component for every place chat appears. Increment 2 is the inbox at /messages: the chat list beside
   the open chat, filling the height the page has left under the pinned chrome. A phone shows one pane at a
   time. Colours are the theme tokens throughout, so dark mode needs nothing of its own. */
.pf-chat-page{display:flex;flex-direction:column;height:calc(100dvh - var(--pf-chrome-h) - 90px);min-height:440px}
.pf-chat-pagehead{display:flex;align-items:center;gap:14px;margin-bottom:12px}
.pf-chat-pagehead h1{font-size:1.3rem;font-weight:700;margin:0;letter-spacing:-.01em}
.pf-chat-notice{flex:1;min-width:0;display:flex;align-items:center;gap:6px;margin:0;font-size:.76rem;color:var(--text-3)}
.pf-chat-notice span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-chat-notice .pf-icon{width:14px;height:14px}
.pf-chat-newbtn{display:inline-flex;align-items:center;gap:5px;flex:none}
.pf-chat-newbtn .pf-icon{width:16px;height:16px}
.pf-chat [hidden],.pf-chat-dialog [hidden]{display:none !important}
.pf-chat{flex:1;min-height:0;display:grid;grid-template-columns:300px minmax(0,1fr);background:var(--surface);
  border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow-sm);overflow:hidden}

/* ---- The chat list ---- */
.pf-chat-list{display:flex;flex-direction:column;min-height:0;border-right:1px solid var(--line)}
.pf-chat-tools{display:flex;gap:6px;padding:10px 10px 6px}
.pf-chat-search{flex:1;min-width:0;height:32px;padding:0 10px;font-size:.84rem;color:var(--text);
  background:var(--surface-2);border:1px solid var(--line-2);border-radius:9px;outline:none}
.pf-chat-search:focus{border-color:var(--brand);box-shadow:0 0 0 3px var(--cyan-soft)}
.pf-chat-chip{flex:none;height:32px;padding:0 11px;font-size:.78rem;font-weight:600;color:var(--text-2);
  background:transparent;border:1px solid var(--line-2);border-radius:999px}
.pf-chat-chip.on{background:var(--cyan-soft);border-color:transparent;color:var(--brand-deep)}
.pf-chat-items{flex:1;min-height:0;overflow-y:auto;padding:2px 6px 8px;overscroll-behavior:contain}
.pf-chat-item{display:flex;align-items:center;gap:10px;width:100%;padding:8px;border:0;border-radius:10px;
  background:none;color:inherit;text-align:left}
.pf-chat-item:hover{background:var(--surface-2)}
.pf-chat-item.is-open{background:var(--cyan-soft)}
.pf-chat-item-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.pf-chat-item-top,.pf-chat-item-bottom{display:flex;align-items:center;gap:6px;min-width:0}
.pf-chat-item-title{flex:1;min-width:0;font-size:.87rem;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-chat-item-time{flex:none;font-size:.7rem;color:var(--text-3)}
.pf-chat-item-preview{flex:1;min-width:0;font-size:.79rem;color:var(--text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-chat-by{color:var(--text-3)}
.pf-chat-item.is-unread .pf-chat-item-title{font-weight:700}
.pf-chat-item.is-unread .pf-chat-item-preview{color:var(--text)}
.pf-chat-item.is-unread .pf-chat-item-time{color:var(--brand);font-weight:600}
.pf-chat-count{flex:none;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:var(--brand);
  color:#fff;font-size:.66rem;font-weight:700;line-height:18px;text-align:center}
.pf-chat-item.is-muted .pf-chat-count{background:var(--text-3)}
/* An unread mention of you: it gets through mute, so it keeps its colour in a muted chat. */
.pf-chat-atmark{flex:none;width:18px;height:18px;border-radius:50%;display:grid;place-items:center;background:var(--brand);
  color:#fff;font-size:.68rem;font-weight:700;line-height:1}
.pf-chat-mutedic{width:14px;height:14px;color:var(--text-3)}
.pf-chat-none{padding:26px 12px;text-align:center;font-size:.84rem;color:var(--text-3)}
.pf-chat-none p{margin:0 0 4px}
.pf-chat-av{flex:none;width:34px;height:34px;border-radius:50%;display:grid;place-items:center;
  background:var(--chip);color:var(--text-2);font-size:.7rem;font-weight:700;letter-spacing:.02em}
.pf-chat-av.is-group .pf-icon{width:17px;height:17px}
.pf-chat-av.is-off{opacity:.5}

/* ---- The open chat ---- */
.pf-chat-conv{position:relative;display:flex;flex-direction:column;min-width:0;min-height:0}
.pf-chat-blank{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;
  color:var(--text-3);font-size:.88rem}
.pf-chat-blank p{margin:0}
.pf-chat-blank-ic{width:40px;height:40px;stroke-width:1.4}
.pf-chat-convhead{display:flex;align-items:center;gap:10px;min-height:56px;padding:9px 12px 9px 16px;
  border-bottom:1px solid var(--line)}
.pf-chat-titles{flex:1;min-width:0}
.pf-chat-title{margin:0;font-size:.98rem;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-chat-sub{font-size:.74rem;color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-chat-convhead .pf-chat-back{display:none}
.pf-chat-ibtn{flex:none;width:32px;height:32px;display:inline-grid;place-items:center;padding:0;border:0;
  border-radius:9px;background:transparent;color:var(--text-2)}
.pf-chat-ibtn:hover{background:var(--surface-2);color:var(--text)}
.pf-chat-ibtn .pf-icon{width:18px;height:18px}
.pf-chat-log{flex:1;min-height:0;overflow-y:auto;padding:10px 18px 10px;overscroll-behavior:contain}
.pf-chat-log:focus-visible{outline:2px solid var(--cyan-soft);outline-offset:-2px}
.pf-chat-earlier{display:block;margin:2px auto 10px;padding:4px 12px;font-size:.76rem;font-weight:600;
  color:var(--brand);background:var(--cyan-soft);border:0;border-radius:999px}
.pf-chat-start{margin:26px 0 10px;text-align:center;font-size:.82rem;color:var(--text-3)}
.pf-chat-day,.pf-chat-new{display:flex;align-items:center;gap:10px;margin:16px 0 6px;font-size:.7rem;
  font-weight:600;color:var(--text-3)}
.pf-chat-day::before,.pf-chat-day::after,.pf-chat-new::before,.pf-chat-new::after{content:"";flex:1;height:1px;background:var(--line-2)}
.pf-chat-new{color:var(--brand)}
.pf-chat-new::before,.pf-chat-new::after{background:var(--brand);opacity:.45}
.pf-chat-sys{margin:10px 0;text-align:center;font-size:.76rem;color:var(--text-3)}
.pf-chat-sys b{font-weight:600;color:var(--text-2)}
.pf-chat-msg{display:flex;align-items:flex-start;gap:8px;margin-top:10px;border-radius:12px;transition:background .6s}
.pf-chat-msg.is-cont{margin-top:2px}
.pf-chat-msg .pf-chat-av{width:30px;height:30px;font-size:.64rem;margin-top:2px}
.pf-chat-av-gap{flex:none;width:30px}
.pf-chat-msg.is-mine{justify-content:flex-end}
.pf-chat-body{min-width:0;max-width:min(72%,640px);display:flex;flex-direction:column;align-items:flex-start}
.pf-chat-msg.is-mine .pf-chat-body{align-items:flex-end}
.pf-chat-meta{display:flex;align-items:baseline;gap:7px;margin:0 3px 2px;font-size:.72rem}
.pf-chat-name{font-weight:600;color:var(--text)}
.pf-chat-time{color:var(--text-3)}
.pf-chat-bubble{padding:7px 11px;border-radius:14px;background:var(--surface-2);color:var(--text);font-size:.89rem;
  line-height:1.42;white-space:pre-wrap;overflow-wrap:anywhere}
.pf-chat-msg:not(.is-cont):not(.is-mine) .pf-chat-bubble{border-top-left-radius:5px}
.pf-chat-msg.is-mine .pf-chat-bubble{background:var(--cyan-soft)}
.pf-chat-msg.is-mine:not(.is-cont) .pf-chat-bubble{border-top-right-radius:5px}
.pf-chat-bubble.is-deleted{background:transparent;border:1px dashed var(--line-2);color:var(--text-3);font-style:italic}
.pf-chat-edited{font-size:.7rem;color:var(--text-3)}
.pf-chat-link{color:var(--brand);text-decoration:underline;text-underline-offset:2px}
.pf-chat-mention{padding:0 3px;border-radius:5px;background:var(--cyan-soft);color:var(--brand-deep);font-weight:600}
.pf-chat-mention.is-off{opacity:.6}
button.pf-chat-mention{border:0;font:inherit;font-weight:600;line-height:inherit;cursor:pointer}
button.pf-chat-mention:hover{text-decoration:underline;text-underline-offset:2px}
.pf-chat-msg.is-mine .pf-chat-mention{background:var(--surface)}
.pf-chat-mention.is-me{background:var(--warn-soft);color:var(--text)}
/* A job or customer: a chip that goes to its page. A job also says where it's up to. */
.pf-chat-ref{display:inline;padding:1px 6px;border-radius:6px;background:var(--surface);border:1px solid var(--line-2);
  color:var(--brand-deep);font-weight:600;text-decoration:none;box-decoration-break:clone;-webkit-box-decoration-break:clone}
.pf-chat-ref:hover{border-color:var(--brand);color:var(--brand-deep)}
.pf-chat-ref.is-off{opacity:.6}
.pf-chat-ref-status{margin-left:2px;font-size:.72rem;font-weight:500;color:var(--text-2);white-space:nowrap}
.pf-chat-msg.is-pending .pf-chat-bubble{opacity:.6}
.pf-chat-msg.is-failed .pf-chat-bubble{opacity:1;box-shadow:inset 0 0 0 1px var(--danger)}
.pf-chat-retry{margin:3px 3px 0;padding:0;border:0;background:none;font-size:.74rem;color:var(--danger);text-decoration:underline}
.pf-chat-msg.is-lit{background:var(--warn-soft)}
.pf-chat-acts{order:-1;display:flex;gap:1px;align-self:center;opacity:0;transition:opacity .12s}
.pf-chat-acts .pf-chat-ibtn{width:28px;height:28px}
.pf-chat-acts .pf-icon{width:15px;height:15px}
.pf-chat-msg:hover .pf-chat-acts,.pf-chat-msg:focus-within .pf-chat-acts,.pf-chat-msg.is-acting .pf-chat-acts{opacity:1}
.pf-chat-editor{display:flex;flex-direction:column;gap:6px;min-width:min(420px,60vw)}
.pf-chat-editbox{width:100%;resize:vertical;padding:6px 9px;font-size:.89rem;line-height:1.42;color:var(--text);
  background:var(--surface);border:1px solid var(--brand);border-radius:10px;outline:none}
.pf-chat-editrow{display:flex;align-items:center;gap:6px}
.pf-chat-editrow .pf-chat-note{flex:1}
.pf-chat-jump{position:absolute;left:50%;bottom:74px;transform:translateX(-50%);display:inline-flex;align-items:center;
  gap:4px;padding:5px 12px;font-size:.76rem;font-weight:600;color:#fff;background:var(--brand);border:0;
  border-radius:999px;box-shadow:var(--shadow)}
.pf-chat-jump .pf-icon{width:14px;height:14px}
.pf-chat-flash{position:absolute;z-index:5;top:64px;left:50%;transform:translateX(-50%);max-width:calc(100% - 32px);
  padding:6px 14px;border-radius:999px;background:var(--danger-soft);color:var(--danger);font-size:.8rem;font-weight:600}
.pf-chat-compose{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:8px;padding:10px 14px 12px;
  border-top:1px solid var(--line)}
.pf-chat-input{width:100%;min-height:38px;max-height:168px;resize:none;padding:8px 12px;font-size:.89rem;line-height:1.42;
  color:var(--text);background:var(--surface-2);border:1px solid var(--line-2);border-radius:12px;outline:none}
.pf-chat-input:focus{border-color:var(--brand);box-shadow:0 0 0 3px var(--cyan-soft)}
.pf-chat-send{width:38px;height:38px;border-radius:50%;background:var(--brand);color:#fff}
.pf-chat-send:hover{background:var(--brand-deep);color:#fff}
.pf-chat-send .pf-icon{width:17px;height:17px;margin:1px 1px 0 0}
.pf-chat-note{grid-column:1/-1;font-size:.72rem;color:var(--text-3)}
.pf-chat-note:empty{display:none}
.pf-chat-note.is-over{color:var(--danger);font-weight:600}
.pf-chat-outside{grid-column:1/-1;font-size:.74rem;font-weight:600;color:var(--warn)}
.pf-chat-outside:empty{display:none}

/* ---- The @ list: people, jobs and customers. Fixed to the page, so a chat's scrolling can't clip it. ---- */
.pf-chat-at{position:fixed;z-index:1060;overflow-y:auto;padding:4px;background:var(--surface);color:var(--text);
  border:1px solid var(--line-2);border-radius:12px;box-shadow:var(--shadow);overscroll-behavior:contain}
.pf-chat-at[hidden]{display:none}
.pf-chat-at-group{padding:6px 8px 3px;font-size:.66rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--text-3)}
.pf-chat-at-opt{display:flex;align-items:center;gap:9px;padding:5px 8px;border-radius:8px;cursor:pointer}
.pf-chat-at-opt:hover{background:var(--surface-2)}
.pf-chat-at-opt.is-active{background:var(--cyan-soft)}
.pf-chat-at-opt .pf-chat-av{width:26px;height:26px;font-size:.6rem}
.pf-chat-at-text{flex:1;min-width:0;display:flex;flex-direction:column}
.pf-chat-at-name{font-size:.85rem;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-chat-at-hint{font-size:.74rem;color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-chat-at-none{padding:8px 10px;font-size:.8rem;color:var(--text-3)}

/* ---- A job's Discussion card (jobs/view.html, chat.js's "job" mode): the thread without a chat list, inside a
   section of the job page, as tall as its messages up to a limit. ---- */
.jv-talk{overflow:hidden}
.pf-chat.pf-chat--job{border:0;border-radius:16px;box-shadow:none;background:transparent}
.pf-chat--job .pf-chat-log{flex:none;max-height:min(460px,60vh)}
.pf-chat--job .pf-chat-convhead{gap:10px;padding:10px 12px 10px 16px;min-height:54px}
.pf-chat--job .pf-chat-log[hidden]~.pf-chat-compose{border-top:0}
.pf-chat-av.is-job{background:var(--cyan-soft);color:var(--brand-deep)}
.pf-chat-faces{display:flex;flex:none;padding-left:6px}
.pf-chat--job .pf-chat-faces .pf-chat-av{width:26px;height:26px;margin-left:-6px;font-size:.58rem;border:2px solid var(--surface)}
.pf-chat-av.is-more{background:var(--chip);color:var(--text-2)}
.pf-chat-follow{flex:none;white-space:nowrap}
.pf-chat-openjob{color:var(--brand);font-weight:600;text-decoration:none}
.pf-chat-openjob:hover{text-decoration:underline}
.jv-talk-also{margin:-2px 0 12px;padding:10px 14px 8px;border:1px dashed var(--line-2);border-radius:14px}
.jv-talk-also h3{margin:0 0 4px;font-size:11.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text-3)}

/* ---- Settings → Messaging: an admin's transcript (settings/messaging/transcript.html) ---- */
.pf-review-text{white-space:pre-wrap;overflow-wrap:anywhere}
.pf-review-text.is-struck{text-decoration:line-through;color:var(--text-3)}
.pf-review-line.is-system{background:var(--surface-2)}
.pf-review-line:target{box-shadow:inset 3px 0 0 var(--brand)}
.pf-review-versions,.pf-review-delete{margin-top:4px}
.pf-review-versions summary,.pf-review-delete summary{cursor:pointer}

/* ---- "Mentioned in chats" on a job or customer page (fragments/chat-mentions.html) ---- */
.pf-mentioned{display:flex;flex-direction:column;gap:2px;margin:0 -6px}
.pf-mentioned-item{display:flex;flex-direction:column;gap:1px;padding:6px;border-radius:8px;color:inherit;text-decoration:none}
.pf-mentioned-item:hover{background:var(--surface-2);color:inherit}
.pf-mentioned-top{display:flex;align-items:baseline;gap:8px;min-width:0}
.pf-mentioned-chat{flex:1;min-width:0;font-size:.82rem;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-mentioned-time{flex:none;font-size:.7rem;color:var(--text-3)}
.pf-mentioned-text{font-size:.8rem;color:var(--text-2);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.pf-mentioned-text b{font-weight:600;color:var(--text)}

/* ---- Dialogs: new chat, add people, rename, "are you sure" ---- */
.pf-chat-dialog{width:min(440px,calc(100vw - 32px));padding:0;border:0;border-radius:16px;background:var(--surface);
  color:var(--text);box-shadow:var(--shadow)}
.pf-chat-dialog::backdrop{background:rgba(3,20,29,.45)}
.pf-chat-dhead{display:flex;align-items:center;gap:8px;padding:14px 12px 4px 18px}
.pf-chat-dhead h2{flex:1;min-width:0;margin:0;font-size:1rem;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-chat-dbody{padding:6px 18px 10px}
.pf-chat-dbody p{margin:0 0 4px;font-size:.88rem;color:var(--text-2)}
.pf-chat-dbody .form-label{display:block;width:100%;margin:0}
.pf-chat-dbody .form-label .form-control{margin-top:4px}
.pf-chat-dbody .pf-chat-hint{margin-top:6px;font-size:.76rem;color:var(--text-3)}
.pf-chat-derror{padding:0 18px 6px;font-size:.8rem;color:var(--danger)}
.pf-chat-derror:empty{display:none}
.pf-chat-dfoot{display:flex;justify-content:flex-end;gap:8px;padding:10px 18px 14px;border-top:1px solid var(--line)}
.pf-chat-picked{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.pf-chat-picked:empty{display:none}
.pf-chat-pchip{display:inline-flex;align-items:center;gap:4px;padding:3px 6px 3px 10px;font-size:.78rem;font-weight:600;
  color:var(--brand-deep);background:var(--cyan-soft);border:0;border-radius:999px}
.pf-chat-pchip .pf-icon{width:12px;height:12px}
.pf-chat-people{max-height:min(44vh,330px);overflow-y:auto;margin:8px -8px 0;padding:0 2px;overscroll-behavior:contain}
.pf-chat-person{display:flex;align-items:center;gap:10px;width:100%;padding:6px 8px;border:0;border-radius:10px;
  background:none;color:inherit;text-align:left}
.pf-chat-person:hover{background:var(--surface-2)}
.pf-chat-person.is-on{background:var(--cyan-soft)}
.pf-chat-person .pf-chat-av{width:30px;height:30px;font-size:.64rem}
.pf-chat-person-text{flex:1;min-width:0;display:flex;flex-direction:column}
.pf-chat-person-name{font-size:.87rem;font-weight:600}
.pf-chat-person-dept{font-size:.73rem;color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-chat-tick{flex:none;width:18px;height:18px;border-radius:50%;border:1.5px solid var(--line-2)}
.pf-chat-person.is-on .pf-chat-tick{border-color:var(--brand);background:var(--brand)
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 12.5l4 4 8-9'/%3E%3C/svg%3E") center/13px no-repeat}
.pf-chat-dbody .pf-chat-groupname{margin-top:10px}

/* ---- Phones: one pane at a time ---- */
@media (max-width:760px){
  .pf-chat-page{height:calc(100dvh - var(--pf-chrome-h) - 60px);min-height:380px}
  .pf-chat-pagehead{flex-wrap:wrap;gap:4px 10px;margin-bottom:10px}
  .pf-chat-pagehead h1{flex:1}
  .pf-chat-notice{order:3;flex-basis:100%;font-size:.7rem}
  .pf-chat-notice span{white-space:normal}
  .pf-chat{grid-template-columns:minmax(0,1fr);border-radius:14px}
  .pf-chat-list{border-right:0}
  .pf-chat:not(.is-open) .pf-chat-conv{display:none}
  .pf-chat.is-open .pf-chat-list{display:none}
  .pf-chat-convhead .pf-chat-back{display:inline-grid}
  /* An open chat takes the whole screen: the heading and notice are on the list screen. */
  .pf-chat-page:has(.pf-chat.is-open) .pf-chat-pagehead{display:none}
  .pf-chat-convhead{padding-left:6px}
  .pf-chat-log{padding:8px 10px}
  .pf-chat-body{max-width:84%}
  .pf-chat-compose{padding:8px 10px 10px}
}

/* ---- One pane at a time: the corner panel and the pop-out (and the inbox on a phone, above) ---- */
.pf-chat.pf-chat--stack{grid-template-columns:minmax(0,1fr)}
.pf-chat--stack:not(.is-open) .pf-chat-conv{display:none}
.pf-chat--stack.is-open .pf-chat-list{display:none}
.pf-chat--stack .pf-chat-convhead .pf-chat-back{display:inline-grid}
.pf-chat--stack .pf-chat-convhead{gap:6px;padding:8px 8px 8px 6px;min-height:52px}
.pf-chat--stack .pf-chat-convhead .pf-chat-av{width:30px;height:30px;font-size:.64rem}
.pf-chat--stack .pf-chat-log{padding:8px 12px}
.pf-chat--stack .pf-chat-body{max-width:84%}
.pf-chat--stack .pf-chat-compose{padding:8px 10px 10px}
.pf-chat--stack .pf-chat-jump{bottom:66px}
.pf-chat-bar{display:flex;align-items:center;gap:2px;padding:10px 8px 2px 14px}
.pf-chat-bartitle{flex:1;min-width:0;margin:0;font-size:1rem;font-weight:700}
.pf-chat-foot{display:flex;align-items:flex-start;gap:6px;margin:0;padding:8px 12px 10px;border-top:1px solid var(--line);
  font-size:.68rem;line-height:1.35;color:var(--text-3)}
.pf-chat-foot .pf-icon{flex:none;width:12px;height:12px;margin-top:1px}

/* ---- The bubble and the corner panel. A page with a sticky Save bar (Automatic emails) lifts both clear of
   it, so the bubble never covers the Save button. ---- */
body:has(.ae-save){--pf-bubble-lift:72px}
.pf-chat-fab{position:fixed;z-index:1045;right:22px;bottom:calc(22px + var(--pf-bubble-lift,0px));width:54px;height:54px;
  display:grid;place-items:center;padding:0;border:0;border-radius:50%;background:var(--brand);color:#fff;
  box-shadow:0 10px 28px rgba(6,38,55,.22),0 2px 6px rgba(6,38,55,.12);transition:transform .15s,background .15s}
.pf-chat-fab:hover{background:var(--brand-deep);transform:translateY(-1px)}
.pf-chat-fab.is-on{background:var(--brand-deep)}
.pf-chat-fab:focus-visible{outline:3px solid var(--cyan-soft);outline-offset:3px}
.pf-chat-fab .pf-icon{width:25px;height:25px;stroke-width:1.9}
.pf-chat-fab-count{position:absolute;top:-4px;right:-4px;min-width:22px;height:22px;padding:0 5px;display:grid;
  place-items:center;border-radius:999px;background:var(--danger);color:#fff;border:2px solid var(--bg);
  font-size:.68rem;font-weight:700;line-height:1}
.pf-chat-panel{position:fixed;z-index:1045;right:22px;bottom:calc(88px + var(--pf-bubble-lift,0px));display:flex;
  width:380px;height:min(560px,calc(100vh - var(--pf-chrome-h) - 110px - var(--pf-bubble-lift,0px)));min-height:320px}
.pf-chat-panel[hidden]{display:none}
.pf-chat-panel>.pf-chat{flex:1;box-shadow:var(--shadow)}
.pf-actions .pf-chat-navicon{display:none}

/* ---- The pop-out: the panel filling a small window of its own ---- */
.pf-chat-popbody{margin:0;height:100dvh;overflow:hidden;background:var(--surface)}
.pf-chat-popout{display:flex;height:100dvh}
.pf-chat-popout>.pf-chat{flex:1;border:0;border-radius:0;box-shadow:none}

@media (max-width:760px){
  /* No floating bubble on a phone: it would sit on the list cards. The bar's chat icon opens the inbox. */
  .pf-chat-fab,.pf-chat-panel{display:none !important}
  .pf-actions .pf-chat-navicon{display:grid}
}
