/* Every rule below reads a var(--po-*) custom property with the original
 * Classic value as its fallback, the active theme's variable set and
 * theme-specific extras are appended at the BOTTOM of this file (see
 * includes/app-themes.php and the note there): the extras must come
 * after these rules to win the cascade, and this file's <style> tag is
 * (historically) never closed here, so the append happens inside it. */
/* Scoped box-sizing normalization (Mike 2026-07-28). Most themes apply a
 * global border-box reset; some (creditweb among them) do not, and there
 * the app's min-heights/widths measured the CONTENT box, inflating
 * buttons (~68px) and drifting multi-column forms like Time Off out of
 * alignment. This pins border-box for the PLUGIN'S OWN elements only,
 * keyed off the app's class prefixes; on themes that already reset,
 * it is a no-op, so the Pro sites render exactly as before. */
.job-form, .job-form *,
.paintops-appbar, .paintops-appbar *,
[class^="paintops-"], [class*=" paintops-"],
[class^="po-"], [class*=" po-"],
[class^="pab-"], [class*=" pab-"],
[class^="pbn-"], [class*=" pbn-"],
[class^="cs-"], [class*=" cs-"],
[class^="veh-"], [class*=" veh-"],
[class^="rl-"], [class*=" rl-"],
.addr-row, .addr-row * {
  box-sizing: border-box;
}

/* App-wide type (2026-08-31 visual refresh). One stack through --po-font
 * so a theme (or a future bundled webfont in assets/vendor/fonts) can
 * swap the face in one place. The default is the modern system stack:
 * zero payload, no remote font host (the wp.org review already made us
 * remove every remote call once, not walking that back), crisp on every
 * device. Same scoping trick as the border-box reset above, the app's
 * own class prefixes, so the host theme's pages are untouched; form
 * controls need the explicit inherit, they never inherit font by default. */
.job-form, .paintops-appbar,
[class^="paintops-"], [class*=" paintops-"],
[class^="po-"], [class*=" po-"],
[class^="pab-"], [class*=" pab-"],
[class^="pbn-"], [class*=" pbn-"],
[class^="cs-"], [class*=" cs-"],
[class^="veh-"], [class*=" veh-"],
[class^="rl-"], [class*=" rl-"],
.addr-row {
  font-family: var(--po-font, -apple-system, "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif);
}
.job-form button, .job-form input, .job-form select, .job-form textarea,
[class^="paintops-"] button, [class*=" paintops-"] button,
[class^="paintops-"] input, [class*=" paintops-"] input,
[class^="paintops-"] select, [class*=" paintops-"] select,
[class^="paintops-"] textarea, [class*=" paintops-"] textarea,
[class^="cs-"] button, [class*=" cs-"] button,
[class^="cs-"] input, [class*=" cs-"] input,
[class^="cs-"] select, [class*=" cs-"] select,
[class^="cs-"] textarea, [class*=" cs-"] textarea {
  font-family: inherit;
}

/* =====================================================================
 * BASE TEXT SIZE (2026-09-13, second look)
 *
 * The app set a font FAMILY but never a font SIZE, so every piece of
 * app text without an explicit rule inherited whatever the host theme
 * puts on <body>. On a current block theme that is 22px. The result was
 * hierarchy upside down: a card title sat at 15px while the helper
 * sentence under it rendered at 22px, and the same sentence changed size
 * from one customer's site to the next. That, more than any color, is
 * what made screens read as unfinished.
 *
 * This sets the app's own body copy and is placed HIGH IN THIS FILE on
 * purpose: every component rule further down is at least as specific, so
 * anything with its own size (titles, labels, table cells, stat figures)
 * keeps it and only the unstyled remainder is caught.
 *
 * Scoped to the app's own containers, never to the theme's pages. The
 * canvas covers the sidebar-shell and header/footer-off layouts (the
 * default on every install since 1.0.281); the component containers
 * carry it through the classic layout too, where there is no canvas.
 * th/td are deliberately NOT listed: tables set their own.
 * ===================================================================== */
.paintops-app-canvas-inner,
.paintops-app-canvas-inner p,
.paintops-app-canvas-inner li,
.paintops-app-canvas-inner label,
.paintops-app-canvas-inner dt,
.paintops-app-canvas-inner dd,
.paintops-app-canvas-inner blockquote,
.job-form, .job-form p, .job-form li, .job-form label,
.cs-financial-box, .cs-financial-box p, .cs-financial-box li,
.job-card, .job-card p,
.paintops-notice, .paintops-notice p {
  font-size: var(--po-fs-body, 14px);
  line-height: 1.55;
}
/* Slightly larger on a phone, where this gets read at arm's length in
 * daylight, and where there is no dense desktop layout to keep compact. */
@media (max-width: 782px) {
  .paintops-app-canvas-inner,
  .paintops-app-canvas-inner p,
  .paintops-app-canvas-inner li,
  .paintops-app-canvas-inner label,
  .job-form, .job-form p, .job-form li, .job-form label,
  .cs-financial-box, .cs-financial-box p, .cs-financial-box li {
    font-size: 15px;
  }
}

/* Sub-headings inside a screen (2026-09-13, second look). 26 bare <h3> and
 * 6 bare <h4> across the app carried no size at all, so like the body copy
 * above they rendered at whatever the host theme puts on a heading, which
 * on the test theme is ~22px: bigger than the card titles they sat inside
 * and, on Time Off, bigger than anything else on the screen. Any heading
 * that already carries an inline size keeps it (inline always wins), so
 * this only catches the ones that were never styled.
 *
 * #printableArea and #estimateArea are excluded: those are the customer's
 * quote and invoice documents, which set their own print-oriented type. */
.paintops-app-canvas-inner h3:not(#printableArea h3),
.job-form h3,
.cs-financial-box h3 {
  font-size: var(--po-fs-card, 15px);
  font-weight: 600;
  line-height: 1.35;
  color: var(--po-heading, #293241);
  margin: var(--po-s5, 24px) 0 var(--po-s3, 12px);
}
.paintops-app-canvas-inner h4:not(#printableArea h4),
.job-form h4,
.cs-financial-box h4 {
  font-size: var(--po-fs-meta, 13px);
  font-weight: 700;
  line-height: 1.4;
  color: var(--po-heading, #293241);
  margin: var(--po-s4, 16px) 0 var(--po-s2, 8px);
}
#printableArea h3, #printableArea h4,
#estimateArea h3, #estimateArea h4 { font-size: revert; font-weight: revert; margin: revert; }

/* =====================================================================
 * DESIGN SYSTEM TOKENS (2026-09-13 visual pass)
 *
 * The app already had a --po-* COLOR set, one per theme (app-themes.php).
 * What it did not have was a scale for the things that actually make
 * software read as mature: type sizes, spacing, and the ink used on a
 * colored structural surface. Those were typed in by hand at each call
 * site, which is why the same idea rendered at 10px, 10.5px and 11px on
 * three different screens, and why panels carried 14px, 18px and 20px
 * padding for no reason.
 *
 * These live in :root in THIS file, and the active theme's :root block
 * is injected AFTER it (paintops_app_theme_css()), so a theme can
 * override any of them without touching this file. No theme defines
 * these today, so nothing conflicts.
 *
 * Two deliberate positions, both from Mike's brief:
 *   STRUCTURE IS NAVY. The sidebar, the phone bar and the classic app
 *   bar all read --po-nav-bg, which every one of the eight themes
 *   already defines as a dark structural color. Chrome carries the
 *   weight; content sits on white.
 *   ACCENT IS FOR INTERACTION, SPARINGLY. --po-accent means "you can
 *   act on this": links, the primary button, the active nav marker.
 *   It is deliberately NOT used for section headers, table headers,
 *   secondary buttons or card chrome any more, which is what made
 *   everything shout at once.
 * ===================================================================== */
:root {
  /* Spacing: a 4px grid. Everything from here down uses these. */
  --po-s1: 4px;
  --po-s2: 8px;
  --po-s3: 12px;
  --po-s4: 16px;
  --po-s5: 24px;
  --po-s6: 32px;

  /* Type ramp. One step per job, no in-between sizes. */
  --po-fs-page:   21px;  /* screen title */
  --po-fs-hero:   30px;  /* the one number a financial screen leads with */
  --po-fs-card:   15px;  /* card / panel title */
  --po-fs-body:   14px;  /* body copy, table cells, form fields */
  --po-fs-meta:   13px;  /* secondary line under a title */
  --po-fs-label:  11px;  /* uppercase eyebrow / column head / stat key */

  /* Ink on a dark structural surface (the sidebar, the phone bar). */
  --po-on-nav:          rgba(255,255,255,.80);
  --po-on-nav-strong:   #ffffff;
  --po-on-nav-muted:    rgba(255,255,255,.56);
  --po-on-nav-line:     rgba(255,255,255,.13);
  --po-on-nav-hover:    rgba(255,255,255,.07);
  --po-on-nav-active:   rgba(255,255,255,.12);
  /* PaintClarity sky blue: the one interactive accent that reads on navy.
   * Used ONLY as the 3px active marker, nowhere else on the rail. */
  --po-nav-marker:      #98C1D9;

  /* ------------------------------------------------------------------
   * THE ACCENT PAIR, declared here so the ~51 var(--po-accent, #4682B4)
   * fallbacks scattered through the stylesheets are never the thing that
   * decides a color. #4682B4 is the historic steel blue and it measures
   * 4.11:1 against white in BOTH of its roles: as a filled button under
   * white text, and as link text on a white card. The primary button is
   * 14px/600, which is not WCAG "large text", so 4.11 was a real AA miss
   * on every surface that emits no theme variables: wp-admin, and a dark
   * theme on the classic layout, which are exactly the surfaces the
   * shape-only rule leaves untokenized.
   *
   * #437bab is a barely deeper version of the same blue and puts both
   * roles at 4.51:1. It is the value classic's --po-accent already
   * carries, so this makes the untokenized case agree with classic
   * instead of diverging from it. Theme CSS is injected AFTER this
   * stylesheet, so any theme that sets --po-accent still wins.
   * Do not lighten it back. Measured 2026-09-14.
   * ------------------------------------------------------------------ */
  --po-accent:          #437bab;
  --po-on-accent:       #ffffff;
}

/* Tabular figures everywhere a number is compared against another number.
 * Proportional digits make a column of money impossible to scan; this is
 * the single cheapest thing that makes a financial screen look correct. */
.cs-fin-table, .veh-table, .po-profit-year, .po-rank-strip,
.cs-fin-gross-box, .po-jrow-cell, .po-stat, .po-biz-sum {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Menu toggles are <button>, not <a href="#"> (2026-09-13 audit, B5).
 * These three controls do nothing but drive JavaScript, so they were
 * anchors wearing a button's clothes: they announced as links, and a
 * middle-click or "open in new tab" did nothing. They are now real
 * <button type="button"> elements. This reset strips the native button
 * chrome so they inherit the typography and box the anchors had, and
 * every existing .pv-more-toggle / .po-jrow-actions-toggle / .pv-dir-gear
 * rule further down this file then applies unchanged. It must stay ABOVE
 * those rules: all of these are single-class selectors, so source order,
 * not specificity, decides, and .pv-dir-gear's own margin-left has to win
 * over the margin:0 here. */
.pv-more-toggle,
.po-jrow-actions-toggle,
.pv-dir-gear {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  line-height: inherit;
  text-align: inherit;
  text-decoration: none;
  cursor: pointer;
}

/* System-wide link color (Mike 2026-09-10): plain in-content links were
 * rendering in the theme's default (often black), so links looked
 * inconsistent across the app. This makes every UNCLASSED link inside the
 * app content use the accent color, one consistent look everywhere. It
 * deliberately targets a:not([class]) only, so classed links, buttons
 * (.paintops-btn), nav items, row titles, toggles, etc., keep their own
 * styling, and inline-styled links (a red delete, a green confirmation)
 * still win via their inline color. */
.paintops-app-canvas-inner a:not([class]) { color: var(--po-accent, #4682B4); }
.paintops-app-canvas-inner a:not([class]):hover { color: var(--po-accent, #4682B4); text-decoration: underline; }

/* Controls that live OUTSIDE a .job-form (2026-09-13, second look): the
 * filter bars and search rows above dashboards and lists. These were
 * whatever the host theme drew, or inline `border:1px solid #ccc` typed at
 * the call site, so the Profits year picker, the Jobs search and the
 * Pipeline search each looked like they came from a different product.
 * .job-form fields are excluded: they keep the 16px / 44px treatment that
 * stops iOS zooming when the crew taps a field on a phone.
 *
 * .po-tallfields joined that exclusion on 2026-09-20 (Mike: "?vehicle=1
 * Please make sure all form field heights are consistent... ?newJob=1 is a
 * good standard"). IT IS A MARKER, NOT A STYLE: put it on a wrapper and the
 * fields inside simply stop matching this compact rule, leaving that page's
 * own stylesheet free to set the tall treatment without a specificity
 * fight. The Vehicle Log needed it because this rule scores (0,4,2) on a
 * text input while ".veh-form-row input" scores (0,3,1), so a form that
 * plainly declared min-height:44px rendered at 34px anyway, and only the
 * field types this rule does not list (number) or that were pinned by an
 * explicit height (date) came out at the intended size. ONE ROW HELD A
 * 45px DATE NEXT TO A 34px DRIVER because of it.
 *
 * ADD THE MARKER TO A WRAPPER RATHER THAN RAISING SPECIFICITY LOCALLY; the
 * arms race is what produced the mess. */
.paintops-app-canvas-inner select:not(:is(.job-form, .po-tallfields) select):not(.po-tallfields),
.paintops-app-canvas-inner input[type="text"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields):not(.po-jobs-search-input),
.paintops-app-canvas-inner input[type="search"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields):not(.po-jobs-search-input),
.paintops-app-canvas-inner input[type="date"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields),
.paintops-app-canvas-inner input[type="url"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields),
.paintops-app-canvas-inner input[type="email"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields),
/* datetime-local and textarea were missing from this list (2026-09-17), so
 * a form mixing them with text inputs drew half its fields in the app's
 * style and half in the browser's. */
.paintops-app-canvas-inner input[type="datetime-local"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields),
.paintops-app-canvas-inner input[type="tel"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields),
.paintops-app-canvas-inner textarea:not(:is(.job-form, .po-tallfields) textarea):not(.po-tallfields) {
  min-height: 34px;
  padding: 6px 10px;
  border: 1px solid var(--po-input-border, #ccd6de);
  border-radius: var(--po-radius-xs, 4px);
  background: var(--po-card-bg, #fff);
  color: var(--po-text, #2b3440);
  font-family: inherit;
  font-size: var(--po-fs-body, 14px);
  line-height: 1.4;
}
@media (max-width: 782px) {
  /* 16px on a phone, same reason as the .job-form rule further down:
   * below it, iOS zooms the page the moment a field takes focus. */
  .paintops-app-canvas-inner select:not(:is(.job-form, .po-tallfields) select):not(.po-tallfields),
  .paintops-app-canvas-inner input[type="text"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields):not(.po-jobs-search-input),
  .paintops-app-canvas-inner input[type="search"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields):not(.po-jobs-search-input),
  .paintops-app-canvas-inner input[type="date"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields),
  .paintops-app-canvas-inner input[type="datetime-local"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields),
  .paintops-app-canvas-inner input[type="tel"]:not(:is(.job-form, .po-tallfields) input):not(.po-tallfields),
  .paintops-app-canvas-inner textarea:not(:is(.job-form, .po-tallfields) textarea):not(.po-tallfields) {
    font-size: 16px;
    min-height: 42px;
  }
}

/* A filter bar: label, control, action, on one baseline. Used above the
 * dashboards that switch period. The label is an eyebrow, not a sentence,
 * because the control next to it already says what it does. */
.po-filterbar {
  display: flex;
  align-items: center;
  gap: var(--po-s2, 8px);
  flex-wrap: wrap;
  margin: 0 0 var(--po-s4, 16px);
}
.po-filterbar label {
  font-size: var(--po-fs-label, 11px);
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--po-text-muted, #5b6673);
  margin: 0;
}
.po-filterbar .paintops-btn,
.po-filterbar .button { min-height: 34px; padding: 6px 14px !important; }

/* A BUTTON SHARING A ROW WITH A TALL FIELD MATCHES IT (2026-09-20).
 * .paintops-btn is 38px + padding = 40px, which is right for a standalone
 * action but reads as a step down when it sits directly beside a 47px
 * input: Rename next to the vehicle name, Assign next to its picker, Add
 * vehicle, Search Receipts. Only buttons INSIDE a .po-tallfields row are
 * touched, so standalone actions further down those pages (Log Trip,
 * Download CSV) keep the app's ordinary button height. */
.paintops-app-canvas-inner .po-tallfields > .paintops-btn,
.paintops-app-canvas-inner .po-tallfields > button.paintops-btn,
.paintops-app-canvas-inner .po-tallfields > div > .paintops-btn {
  min-height: 47px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* THE "SCAN WITH AI / MANUALLY ENTER" CHOOSER (Mike 2026-09-21: "manually
 * enter and scan receipt with ai buttons need spacing"). All three copies
 * of it (File a Receipt, a job's expenses, the vehicle log) separated the
 * two buttons with margin-left:8px on the second one. A left margin does
 * nothing once the pair wraps, which on a phone is always, so the two
 * buttons stacked flush against each other. A flex row with a real gap
 * spaces them on one line and on two. */
.po-choice-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}
/* The explanatory line above the buttons is a block, so it takes the whole
 * first row and the buttons sit under it as before. */
.po-choice-row > p { flex: 1 0 100%; }

/* RECEIPT SEARCH DATES (Mike 2026-09-20: "Please check ?newReceipt=1 as
 * well. Same thing. ?newJob=1 is a good standard.").
 *
 * The two date inputs carried style="height:36px" inline. The canvas rule
 * above adds 6px of padding and those inputs computed as content-box, so
 * 36 + 12 + 2 borders rendered 50px, against a 44px Search Receipts button
 * sitting in the same row. An inline height with no box-sizing is how a
 * field ends up taller than the number written on it.
 *
 * Matched to ?newJob=1 and to the button beside them. This lives in the
 * app stylesheet rather than a page one because includes/file-receipt.php
 * enqueues no stylesheet of its own; the class keeps the reach to that
 * one row. */
.paintops-app-canvas-inner input.po-rsearch-date[type="date"] {
  height: 47px;
  min-height: 44px;
  padding: 0 10px;
  font-size: 16px;
  line-height: 1.4;
  box-sizing: border-box;
}
@media (max-width: 782px) {
  .po-filterbar .paintops-btn,
  .po-filterbar .button { min-height: 42px; padding: 9px 16px !important; }
}

/* Form-submission notice banners, a full-width mint-green row on success and
 * a red row on error, shown after a POST reloads the page. Replaces the old
 * plain colored-text notices (was color:green / color:#b00) app-wide, so a
 * "Financials saved." / "Invoice created." confirmation is clearly visible
 * even when the reload takes a beat. */
.paintops-notice {
  padding: 12px 16px;
  border-radius: var(--po-radius, 8px);
  margin: 0 0 14px;
  font-size: 14px;
  font-weight: 600;
  /* A regular 1px border, never a thick accent bar on the left or top
   * (Mike 2026-09-29). */
  border: 1px solid transparent;
  line-height: 1.4;
  animation: paintops-notice-in .18s ease-out;
}
.paintops-notice a { color: inherit; text-decoration: underline; }
.paintops-notice.is-success { background: #e6f6ec; color: #1a7a43; border-color: #b9e2c9; }
.paintops-notice.is-error   { background: #fdecea; color: #b3261e; border-color: #f3c2bd; }
/* THE THIRD VARIANT, added 2026-09-24 with the duplicate receipt check.
 * Success and error were the only two, so anything that needed to say
 * "stop and look at this, nothing is broken" had to borrow the red one
 * and read as a failure, or borrow none and read as loose bold text.
 * The amber matches the alert panels already used on the Business page. */
.paintops-notice.is-warning { background: #fdf8ef; color: #8a6218; border-color: #e3c27e; }
/* THE FOURTH VARIANT, added 2026-09-28 with the request importer, for the
 * same reason the third one was added: a standing statement of fact that the
 * reader should not skip, but which is not a success, not a failure, and not
 * a warning either. "Everything comes in as a request, never as a won job" is
 * reassurance; in amber it reads as a problem, in green as something that just
 * happened, and with no variant at all it reads as loose bold text, which is
 * exactly what the note above this line warned about. Neutral blue, taken from
 * --po-heading so it belongs to the same palette as every screen title. */
.paintops-notice.is-info    { background: #eef3fb; color: #2f5aae; border-color: #c4d4ee; }
@keyframes paintops-notice-in { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: none; } }

/* Standardized screen title (2026-07-19): every app screen renders its
 * name through paintops_screen_title() (manage-jobs.php), an h2 with
 * this class, so titles are identical in size, weight, color, and
 * spacing everywhere (they used to vary screen to screen or be missing
 * entirely). Deliberately h2 (Mike's call). */
.po-screen-title {
  font-size: var(--po-fs-page, 21px);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--po-heading, #2f5aae);
  margin: 0 0 var(--po-s3, 12px);
  line-height: 1.3;
}
@media (max-width: 782px) {
  .po-screen-title { font-size: 19px; }
}

/* Page masthead (2026-09-13 visual pass). The title, an optional one-line
 * context string, and a rule that closes the header off from the content.
 * The rule is what makes a screen read as a DOCUMENT with a header rather
 * than a title floating above a pile of boxes, and it costs one border.
 * The context line is rendered by paintops_screen_title($title, $context)
 * only where the page already holds the data; it is never invented. */
/* `body ` prefix on purpose (0,1,1): block themes ship a global
 * `:root :where(.is-layout-flow) > *  { margin-block-end: 0 }` at the same
 * 0,1,0 specificity as a bare class, printed after the plugin's stylesheet,
 * which silently zeroed the masthead's bottom margin in the classic layout.
 * The extra element in the selector settles it without reaching for
 * !important. */
body .po-page-head {
  margin: 0 0 var(--po-s5, 24px);
  padding-bottom: var(--po-s3, 12px);
  border-bottom: 1px solid var(--po-card-border, #d7e3ec);
}
.po-page-head .po-screen-title { margin-bottom: 0; }
.po-page-context {
  margin: var(--po-s1, 4px) 0 0;
  font-size: var(--po-fs-meta, 13px);
  line-height: 1.45;
  color: var(--po-text-muted, #5b6673);
}
.po-page-context strong { color: var(--po-text, #2b3440); font-weight: 600; }
/* Separators inside the context line, so it reads as one sentence of facts
 * rather than a stack. */
.po-page-context .po-ctx-sep { color: var(--po-text-soft, #97a2ae); margin: 0 var(--po-s2, 8px); }

/* A screen title NOT wrapped in a masthead still gets the rule, so the
 * treatment is consistent on pages that build their own header markup. */
.paintops-app-canvas-inner > .po-screen-title,
.paintops-app-canvas-inner > .job-form > .po-screen-title {
  padding-bottom: var(--po-s3, 12px);
  border-bottom: 1px solid var(--po-card-border, #d7e3ec);
  margin-bottom: var(--po-s5, 24px);
}
/* Screen titles became <h1> wherever the plugin renders the whole document
 * (2026-09-13 audit, B4 - see paintops_screen_title_tag() in
 * manage-jobs.php). A bare `h1 {}` rule in a host theme loses to the
 * single-class rule above, but a descendant rule like `.entry-content h1`
 * would win, so pin the app's own canvas explicitly and keep h1 and h2
 * looking identical. */
.paintops-app-canvas-inner h1.po-screen-title,
.paintops-app-canvas-inner h2.po-screen-title {
  font-size: var(--po-fs-page, 21px);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--po-heading, #2f5aae);
  line-height: 1.3;
  text-transform: none;
}
@media (max-width: 782px) {
  .paintops-app-canvas-inner h1.po-screen-title,
  .paintops-app-canvas-inner h2.po-screen-title { font-size: 19px; }
}

/* Address Row Styles */
.addr-row {
  display: grid;
  grid-template-columns: 2fr 1fr 0.5fr 0.8fr; /* Address | City | State | Zip */
  gap: 1rem;
  align-items: end; /* line up inputs */
}

.addr-row label {
  display: block;
  font-weight: 600;
  margin-bottom: .25rem;
}

.addr-row input {
  width: 100%;
  padding: .5rem;
  border: 1px solid var(--po-input-border, #ccc);
  border-radius: var(--po-radius-xs, 4px);
}

/* Job Form Styles */
.job-form {
  max-width: 100%;   /* Optional: limit width for readability */
  margin: 2rem auto;
  /* Reserves room at the bottom so the fixed Save bar (.paintops-sticky-save
     below) never overlaps the last field or the "Add Color"/"Remove"
     buttons underneath it. */
  padding-bottom: 90px;
}

/* Job Form Section Headings
 *
 * Used to break the New Job/Quote form (Job Info, Customer Info,
 * Scheduling, etc.) into visibly distinct sections instead of one long
 * scroll of labels/inputs that all look the same.
 */
/* Section headers (Mike 2026-09-10): dropped the boxed treatment (soft
 * background + 4px left accent bar + radius) that pulled too much attention,
 * now just a clean bold uppercase heading. Used across the app's forms and
 * dashboard section titles, so this restyles all of them at once. */
/* (2026-09-13 visual pass) These were 105% bold in the HEADING color, which
 * put them in competition with the screen title for the top of the page.
 * A section label's job is to divide, not to announce, so it is now a small
 * letterspaced eyebrow in the muted ink. Hierarchy comes from size and
 * color, not from making everything bold. */
.job-form-section {
  display: flex;
  align-items: center;
  gap: var(--po-s2, 8px);
  margin: var(--po-s6, 32px) 0 var(--po-s3, 12px);
  padding: 0 0 var(--po-s2, 8px);
  border-bottom: 1px solid var(--po-line, #e6eaef);
  color: var(--po-text-muted, #5b6673);
  font-size: var(--po-fs-label, 11px);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.job-form-section:first-of-type {
  margin-top: 0;
}

/* SECTION SEPARATION ON THE REQUEST / JOB FORM (Mike 2026-09-20: "please
 * separate sections with css. Subtle but will show that each area is
 * distinct").
 *
 * SCOPED TO .po-reqform ON PURPOSE. .job-form-section above is shared by
 * dashboards and most of the app's other forms, so restyling it globally
 * to fix one screen would move a dozen pages nobody asked about.
 *
 * No wrappers were added around the three stretches of fields. The headings
 * are plain siblings in a long form with PHP branching between them, and
 * wrapping them means guessing where each one ends; get that wrong and the
 * browser silently repairs the markup so it LOOKS fine and is not. The
 * heading instead becomes the cap of its section: a tinted band with a rule
 * under it and real air above, which chunks the form into three without
 * touching a line of structure. If you want full bordered cards per
 * section, that is the wrapper job, and worth doing deliberately. */
.po-reqform .job-form-section {
  /* 40px literal: the scale stops at --po-s6 (32px) and inventing an
   * --po-s7 for one rule would put a token in the sheet that nothing
   * else uses. */
  margin: 40px 0 var(--po-s4, 16px);
  padding: 10px 12px;
  background: var(--po-tile-bg, #f6f8fa);
  border: 1px solid var(--po-card-border, #e3e6ea);
  border-radius: var(--po-radius-xs, 4px);
  color: var(--po-heading, #293241);
  font-size: var(--po-fs-label, 11px);
}
/* The first section still needs its air, because the "* Required fields"
 * line and its rule sit directly above it. */
.po-reqform .job-form-section:first-of-type {
  margin-top: var(--po-s5, 24px);
}
/* Tighter on a phone, where 40px of air between sections is most of a
 * thumb-scroll for no added clarity. */
@media (max-width: 600px) {
  .po-reqform .job-form-section { margin-top: var(--po-s6, 32px); padding: 9px 10px; }
}

/* THE EXISTING-CUSTOMER PICKER: select, type-to-filter box, and the note
 * that appears when the list is capped (Mike 2026-09-20). */
/* The filter reads as a TOOL, not as a field to fill in. Sitting directly
 * under the "Select Existing Customer" label in full field dress, it looked
 * like the control that label names, and the select below it looked like a
 * second question. Smaller, lighter, and tight to the select so the two
 * read as one group. */
.po-custpick-filter {
  margin: 0 0 6px !important;
  font-size: 13.5px !important;
  padding: 7px 10px !important;
  background: var(--po-tile-bg, #f6f8fa) !important;
  border: 1px solid var(--po-card-border, #e3e6ea) !important;
}
.po-custpick-filter:focus {
  background: var(--po-card-bg, #fff) !important;
}
.po-custpick select {
  margin-top: 0 !important;
}
.po-custpick-count {
  margin: 6px 0 0;
  font-size: var(--po-fs-meta, 13px);
  color: var(--po-text-muted, #5b6673);
}
.po-custpick-more {
  margin: 8px 0 20px;
  font-size: var(--po-fs-meta, 13px);
  line-height: 1.5;
  color: var(--po-text-muted, #5b6673);
}

/* Job Financials (?jobFinancials=X), replaces the plain bordered box,
 * pipe-separated summary line, and default WP-admin "widefat striped"
 * tables with something that matches the rest of the app: a card, a
 * row of at-a-glance stat tiles (profit colored green/red), and
 * branded table headers. */
/* (2026-09-13 visual pass) Panels are defined by a hairline border, not by
 * a shadow. A shadow says "this floats above the page", which is true of a
 * menu and false of a table of expenses; using it for both is what makes an
 * app read as a pile of widgets. Shadows are now reserved for things that
 * genuinely overlay (menus, sheets, the row Actions panel). */
.cs-financial-box {
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius, 6px);
  padding: var(--po-s4, 16px) var(--po-s4, 16px) var(--po-s5, 24px);
  margin: var(--po-s4, 16px) 0;
  background: var(--po-card-bg, #fff);
  box-shadow: none;
}

.cs-financial-box > h3:first-child {
  margin: 0 0 var(--po-s3, 12px);
  color: var(--po-heading, #2f5aae);
  font-size: var(--po-fs-card, 15px);
  font-weight: 600;
  letter-spacing: -0.005em;
}

/* Add Expense form (paintops_render_worker_add_receipt_box): the
 * Vendor/Category/Amount/Date fields sit in two compact columns on
 * desktop instead of one long stack (the old layout was a lot of
 * scrolling for a quick receipt entry) and the inputs fill their
 * column instead of rendering at tiny default widths. Back to a
 * single column on phones, where the stack is the right shape. */
.po-expense-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 20px;
  max-width: 640px;
  align-items: start;
}
.po-expense-grid p { margin: 6px 0 10px; }
.po-expense-grid select,
.po-expense-grid input[type="text"],
.po-expense-grid input[type="date"] { width: 100%; max-width: 100%; }
@media (max-width: 782px) {
  .po-expense-grid { grid-template-columns: 1fr; }
}

.cs-fin-summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 10px;
  margin: 14px 0 22px;
}

/* (2026-09-13, second look) The job stat tiles were tinted panels with
 * centered figures, and the PRIMARY one (Profit, the whole point of the
 * screen) was marked by washing it in --po-accent-soft. On a theme whose
 * tile background is already tinted, that wash read GREY next to its
 * siblings, so the most important number on the page looked like the
 * disabled one. Emphasis is inverted by a tint and carried properly by
 * type and a rule, so: every tile is now a flat white panel with its label
 * as an eyebrow and its figure left-aligned (centered numbers cannot be
 * scanned down a row). The primary tile used to carry a 2px accent rule
 * across its top; Mike asked for it to go (2026-09-29, "not to style
 * contents with the shadow box"), so every tile is the same plain
 * hairline card and the primary figure is carried by its position and
 * its positive/negative color alone. */
.cs-fin-stat {
  border: 1px solid var(--po-card-border, #e6e6e6);
  border-radius: var(--po-radius-xs, 4px);
  padding: var(--po-s3, 12px);
  background: var(--po-card-bg, #fff);
  text-align: left;
}

.cs-fin-stat span {
  display: block;
  font-size: var(--po-fs-label, 11px);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--po-text-muted, #5b6673);
  margin-bottom: var(--po-s1, 4px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cs-fin-stat strong {
  display: block;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--po-heading, #293241);
  font-variant-numeric: tabular-nums;
}

/* Request Performance card on the Jobs home (Mike 2026-09-15: "the content
 * tables have a different feeling than the rest of the page"). The all-caps
 * letterspaced eyebrow is the right treatment for a SECTION label, which is
 * why the heading above this card keeps it, but four of them stacked inside
 * one small card read as shouting next to the plain rows above. Scoped to
 * this card on purpose: the Financials and Profits screens are dense
 * money tables where the uppercase column heads are doing real work, and
 * they are not what Mike was looking at. */
.po-reqperf .cs-fin-stat span,
.po-reqperf .cs-fin-table th {
  text-transform: none;
  letter-spacing: 0;
  font-size: 12px;
  font-weight: 600;
}

.cs-fin-stat.cs-fin-positive strong { color: var(--po-positive, #1e7e34); }
.cs-fin-stat.cs-fin-negative strong { color: var(--po-negative, #c0392b); }

/* Profits page year summary (Mike 2026-09-10): replaces the row of 7 equal
 * bordered pills with one card that leads with the Profit hero and lists the
 * rest as light stats. This Year and Last Year share it for easy comparison.
 * Scoped to .po-profit-* so nothing else changes. */
/* (2026-09-13 visual pass) Was a 14px-radius card carrying TWO stacked
 * shadows. Now a flat panel with a hairline: the numbers are the subject,
 * the container should be invisible. */
.po-profit-year { display: flex; flex-wrap: wrap; align-items: center; gap: var(--po-s5, 24px) var(--po-s5, 24px); background: var(--po-card-bg, #fff); border: 1px solid var(--po-card-border, #e3e6ea); border-radius: var(--po-radius, 6px); padding: var(--po-s4, 16px) var(--po-s5, 24px); margin: 0 0 var(--po-s4, 16px); box-shadow: none; }
/* 236px, not 200: at 200 the sub-line ("80.6% margin, 0.0% YoY growth")
 * wrapped with one word stranded on its own row under the hero figure. */
.po-profit-hero { flex: 0 1 236px; min-width: 0; }
.po-profit-k { display: block; font-size: var(--po-fs-label, 11px); font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--po-text-muted, #6b7480); }
.po-profit-v { display: block; font-size: var(--po-fs-hero, 30px); font-weight: 600; letter-spacing: -.02em; line-height: 1.1; margin-top: var(--po-s1, 4px); color: var(--po-heading, #2f5aae); white-space: nowrap; font-variant-numeric: tabular-nums; }
.po-profit-v.cs-fin-positive { color: var(--po-positive, #1e7e34); }
.po-profit-v.cs-fin-negative { color: var(--po-negative, #c0392b); }
.po-profit-sub { display: block; font-size: var(--po-fs-meta, 13px); color: var(--po-text-muted, #6b7480); margin-top: var(--po-s1, 4px); }
.po-profit-sub strong { color: var(--po-text, #2b3440); }
.po-profit-vline { width: 1px; align-self: stretch; background: var(--po-line, #e3e6ea); }
/* Five columns on a full-width laptop: seven stats read as 5 + 2 and every
 * figure sits on a shared baseline, which is what makes a stat strip
 * scannable. The count is auto-fit with a 104px floor rather than a hard
 * repeat(5), because a hard 5 held its column count all the way down to a
 * 720px card (a 1024px laptop, where the sidebar is still showing) and
 * squeezed each column to 61px: the labels ellipsed and "$72,859.13" ran
 * past its own cell into the next one. The floor is 116px because that is
 * what the longest label, "TOTAL EXPENSES", measures on one line, and the
 * column gap is 12px rather than 16px so five of those still fit a
 * full-width card. So the strip drops to four columns, then three, before
 * anything is squeezed. The labels are held to one line here (nowrap +
 * ellipsis below) so a two-word label like "TOTAL EXPENSES" cannot wrap
 * and push its value out of line with its neighbors. The flex basis is
 * what lets this whole block drop to its own row when the card is too
 * narrow to seat it beside the hero; min-width: 0 stops it being squeezed
 * to nothing instead. Phone layout is in the max-width: 760px block. */
.po-profit-substats { display: grid; grid-template-columns: repeat(auto-fit, minmax(116px, 1fr)); gap: var(--po-s4, 16px) var(--po-s3, 12px); flex: 1 1 300px; min-width: 0; align-content: center; }
.po-profit-stat span { display: block; font-size: var(--po-fs-label, 11px); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--po-text-soft, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.po-profit-stat strong { display: block; font-size: 17px; font-weight: 600; margin-top: var(--po-s1, 4px); font-variant-numeric: tabular-nums; }
.po-profit-stat strong.cs-fin-positive { color: var(--po-positive, #1e7e34); }
.po-profit-stat strong.cs-fin-negative { color: var(--po-negative, #c0392b); }
/* Phone and small tablet (Mike 2026-09-20, "the text is garbled on a mobile
 * device"). The card was one flex line and the hero was pinned at a rigid
 * flex: 0 0 236px, so on a phone the stats block had no way to leave that
 * line: it kept its place beside the hero and shrank to 16px wide. Every
 * label ellipsed down to two letters ("SALES TA...") and the figures ran
 * straight into each other. Below 760px the card now stacks outright, hero
 * first with a hairline under it, then the stats as an auto-fitting grid
 * that picks its own column count from the width it actually has: four
 * across a small tablet, two on a phone, one on a very narrow screen. The
 * labels wrap here instead of clipping, because at this width there is no
 * spare room to clip into. The 236px hero basis and the 5-column desktop
 * grid above are untouched, so nothing changes on a laptop. */
@media (max-width: 760px){
  .po-profit-year { display: block; padding: var(--po-s4, 16px); }
  .po-profit-vline { display: none; }
  .po-profit-hero { margin: 0 0 var(--po-s3, 12px); padding-bottom: var(--po-s3, 12px); border-bottom: 1px solid var(--po-line, #e3e6ea); }
  .po-profit-substats { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: var(--po-s3, 12px) var(--po-s4, 16px); }
  .po-profit-stat span { white-space: normal; overflow: visible; text-overflow: clip; }
  .po-profit-v { font-size: 26px; }
}

/* Ranking cards: inline stat strip instead of 5 boxed tiles (Mike 2026-09-10),
 * led by Profit/hr. */
.po-rank-strip { display: flex; flex-wrap: wrap; gap: 14px 20px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--po-line, #e3e6ea); }
.po-rank-stat span { display: block; font-size: var(--po-fs-label, 11px); font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--po-text-soft, #9aa5b1); }
.po-rank-stat strong { display: block; font-size: var(--po-fs-card, 15px); font-weight: 600; margin-top: var(--po-s1, 4px); font-variant-numeric: tabular-nums; }
.po-rank-stat strong.cs-fin-positive { color: var(--po-positive, #1e7e34); }
.po-rank-stat strong.cs-fin-negative { color: var(--po-negative, #c0392b); }

.cs-fin-table {
  width: 100%;
  border-collapse: collapse;
  margin: 10px 0 6px;
}

/* (2026-09-13 visual pass) Table heads were a tinted accent band with a 2px
 * accent rule under them, which made every table shout before you read a
 * single value. A column head is a LABEL: small, letterspaced, muted, on the
 * card surface, closed with one hairline. The data is what should be dark. */
.cs-fin-table th {
  background: transparent;
  color: var(--po-text-muted, #5b6673);
  text-align: left;
  padding: var(--po-s2, 8px) var(--po-s3, 12px);
  font-size: var(--po-fs-label, 11px);
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  white-space: nowrap;
  border-bottom: 1px solid var(--po-card-border, #d7e3ec);
}

.cs-fin-table td {
  padding: var(--po-s3, 12px);
  border-bottom: 1px solid var(--po-line, #eee);
  vertical-align: middle;
  font-size: var(--po-fs-body, 14px);
  color: var(--po-text, #2b3440);
}
/* The last row closes on the panel edge, not on a dangling rule. */
.cs-fin-table > tbody > tr:last-child > td { border-bottom: 0; }

/* Money and count columns align right so decimal points stack and a column
 * can be read down. The .po-num class is applied by app-fin-table-labels.js,
 * which already walks every .cs-fin-table and .veh-table to copy the column
 * heads onto the cells for the phone layout; it only marks a column where
 * EVERY non-empty cell in it is a bare number, so a mixed column keeps its
 * left alignment rather than being guessed at. */
.cs-fin-table th.po-num,
.cs-fin-table td.po-num,
.veh-table th.po-num,
.veh-table td.po-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 782px) {
  /* The phone layout turns each row into label/value pairs that are already
   * right-aligned, so the desktop rule would be a no-op at best and fight
   * the flex row at worst. */
  .cs-fin-table td.po-num, .veh-table td.po-num { text-align: right; }
}

.cs-fin-table tbody tr:hover td {
  background: var(--po-hover, #f8fafc);
}

/* Data tables (Quote Performance, Profit by Classification, Outstanding
 * Balances, rankings, the Vehicle expense log, etc.) on a phone. A sideways-
 * scrolling table clips values and never reads well, so instead each row
 * collapses into a vertical "Label: value" card, like the calendar's stacked
 * view. The column headers are copied onto each cell as data-label by
 * paintops_cs_fin_table_labelize() (printed once via wp_footer), so this works
 * for every .cs-fin-table AND .veh-table without editing each table's markup.
 * Desktop is unchanged (normal table). */
.cs-fin-tablewrap { margin: 10px 0 6px; }
@media (min-width: 783px) {
  .cs-fin-tablewrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}
@media (max-width: 782px) {
  .cs-fin-table, .cs-fin-table > tbody, .cs-fin-table > tbody > tr, .cs-fin-table > tbody > tr > td,
  .veh-table, .veh-table > tbody, .veh-table > tbody > tr, .veh-table > tbody > tr > td {
    display: block !important;
    width: auto !important;
    min-width: 0 !important;
  }
  .cs-fin-table > thead, .veh-table > thead { display: none !important; }
  .cs-fin-table > tbody > tr, .veh-table > tbody > tr {
    border: 1px solid var(--po-card-border, #e2e6ea);
    border-radius: 8px;
    margin: 0 0 12px;
    padding: 4px 12px;
    background: var(--po-card-bg, #fff);
  }
  .cs-fin-table > tbody > tr > td, .veh-table > tbody > tr > td {
    display: flex !important;
    justify-content: space-between;
    align-items: baseline;
    gap: 16px;
    text-align: right;
    padding: 8px 0 !important;
    border: 0 !important;
    border-bottom: 1px solid var(--po-line, #eee) !important;
    white-space: normal !important;
  }
  .cs-fin-table > tbody > tr > td:last-child, .veh-table > tbody > tr > td:last-child { border-bottom: 0 !important; }
  /* (2026-07-19, Mike's overflow report) A stacked cell whose VALUE is a
   * whole form (the Vehicles rename row: input + Rename button) can't fit
   * beside its ::before label on a phone, and because these cells are
   * unwrappable flex rows the td forced the page wider than the viewport,
   * the entire app scrolled sideways. Let stacked cells wrap, and on
   * phones let the rename form take its own full-width line under the
   * label with the input free to shrink (its desktop 220px floor is what
   * blew the width). */
  .cs-fin-table > tbody > tr > td, .veh-table > tbody > tr > td { flex-wrap: wrap; }
  .veh-table > tbody > tr > td form { flex: 1 1 100%; flex-wrap: wrap; }
  .veh-table > tbody > tr > td form input[type="text"] { min-width: 0 !important; max-width: none !important; flex: 1 1 140px !important; }
  .veh-table .paintops-btn { flex: 0 0 auto; }
  .cs-fin-table > tbody > tr > td[data-label]::before,
  .veh-table > tbody > tr > td[data-label]::before {
    content: attr(data-label);
    font-weight: 600;
    color: var(--po-text-muted, #667581);
    text-align: left;
    white-space: nowrap;
  }
  /* A cell that spans the row (e.g. the Profit-by-Classification detail
   * drawer that holds a nested table) shouldn't be a label/value flex row. */
  .cs-fin-table > tbody > tr > td[colspan], .veh-table > tbody > tr > td[colspan] { display: block !important; padding: 6px 0 !important; }
}

/* (2026-09-13 visual pass) The totals box under a table. Tightened, and the
 * label set small so the figure inside it is what the eye lands on. */
.cs-fin-gross-box {
  border: 1px solid var(--po-card-border, #e6e6e6);
  border-radius: var(--po-radius-xs, 4px);
  padding: var(--po-s3, 12px) var(--po-s4, 16px);
  background: var(--po-tile-bg, #fafbfc);
  margin: var(--po-s4, 16px) 0 var(--po-s5, 24px);
  max-width: 320px;
  font-variant-numeric: tabular-nums;
}
.cs-fin-gross-box strong {
  display: block;
  font-size: var(--po-fs-label, 11px);
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--po-text-muted, #5b6673);
  margin-bottom: var(--po-s1, 4px);
}

/* Email Quote/Estimate/Bid (?estimates=1&csaction=send&getEstId=X),
 * replaces the plain heading + bare <p><label><input> stack (whatever
 * borders the theme happened to give inputs by default) with the same
 * card look used elsewhere in the app, and consistent field spacing. */
.paintops-email-quote-box {
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius, 8px);
  padding: 18px 20px 22px;
  margin: 20px 0;
  background: var(--po-card-bg, #fff);
  box-shadow: var(--po-card-shadow, 0 1px 2px rgba(0,0,0,.04));
  max-width: 720px;
}

.paintops-email-quote-box h2 {
  margin-top: 0;
  color: var(--po-heading, #2f5aae);
  font-size: 130%;
}

.paintops-email-quote-box p {
  margin-bottom: 18px;
}

.paintops-email-quote-box label {
  display: block;
  font-weight: 600;
  margin-bottom: 5px;
}

.paintops-email-quote-box input[type="email"],
.paintops-email-quote-box input[type="text"],
.paintops-email-quote-box textarea {
  width: 100%;
  max-width: 100%;
  padding: 11px 12px;
  min-height: 44px;
  border: 1px solid var(--po-input-border, #ccc);
  border-radius: var(--po-radius-xs, 5px);
  font-family: inherit;
  font-size: 16px;
  box-sizing: border-box;
}

.paintops-email-quote-box textarea {
  line-height: 1.5;
}

.paintops-email-quote-box input[type="file"] {
  margin-top: 6px;
}

/* Profits page (?profits=1), the Top/Bottom/All job cards in the
 * Profit Per Hour rankings. Reuses the same card look and
 * cs-fin-positive/cs-fin-negative coloring as Job Financials so the
 * whole reporting side of the app reads as one system instead of every
 * report inventing its own inline styles. (The separate year-over-year
 * comparison cards that used to live here were folded into the This
 * Year So Far / Last Year stat grids instead, see
 * paintops_render_yearly_financial_dashboard() in job-financials-addon.php,
 * so their .cs-year-* rules were removed along with them.) */
.cs-rank-card {
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius, 8px);
  padding: 15px;
  margin-bottom: 15px;
  background: var(--po-card-bg, #fff);
}

.cs-rank-card .cs-rank-customer {
  font-size: 16px;
  margin-bottom: 10px;
  line-height: 1.5;
}

/* Toggle links (i.e. "Copy Title & Description From a Past Job") that
 * show/hide a .hidden-box below them. inline-flex + gap (rather than the
 * plain inline-block this started as) lines up the leading dashicon with
 * the text, same convention as .pv-quick-actions a elsewhere. */
.job-form-toggle {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: -6px 0 14px;
  font-size: 90%;
  color: var(--po-accent, #4682B4);
  text-decoration: underline dotted;
  cursor: pointer;
}

.job-form-toggle .dashicons {
  font-size: 15px;
  width: 15px;
  height: 15px;
  text-decoration: none;
}

/* Marks an actually-required label, since "all fields required unless
 * noted" (stated once at the top of the form) is easy to forget by the
 * time you're scrolling through a long form. */
.paintops-required {
  color: var(--po-negative, #b00);
  font-weight: 700;
}

/* Small "(i)" hover tooltip, used next to a label instead of a
 * `<p class="description">` line underneath, for a short note that
 * doesn't need to take up permanent space on the form (e.g. "manage
 * this list elsewhere," or "this only matters when X"). */
.paintops-tooltip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  margin-left: 4px;
  border-radius: 50%;
  background: var(--po-tooltip-bg, #ccd6e4);
  /* The circle's background is a theme token, so its ink has to be one too.
     #fff measured 1.47:1 on the classic circle (2026-09-14). The fallback
     pair below is internally consistent (9.53:1) for the shape-only case,
     where no theme variables are emitted. */
  color: var(--po-on-tooltip, #1f2d3d);
  font-size: 11px;
  font-weight: 700;
  font-style: normal;
  line-height: 16px;
  text-align: center;
  cursor: help;
  vertical-align: middle;
}

.paintops-tooltip:hover,
.paintops-tooltip:focus {
  background: var(--po-accent, #4682B4);
}

/* BELOW THE ICON, LEFT-ANCHORED, AND ABOVE THE CHROME (Mike 2026-09-20:
 * "the tooltip runs into the menu and some of the text is cut off. On
 * mobile, part of the text exits the screen").
 *
 * Three separate faults, all in these few lines:
 *   1. bottom: 130% put the bubble ABOVE the icon, so a tooltip on a field
 *      near the top of the page opened straight into the sticky app bar.
 *   2. z-index: 20 lost to that bar (the sticky save bar alone is 100), so
 *      the overlap clipped the text instead of covering the bar.
 *   3. left: 50% + translateX(-50%) centered a 240px bubble on a 16px icon,
 *      hanging 120px to its left. These icons sit beside labels at the left
 *      edge of the form, so on a phone that 120px was off the screen.
 *
 * Now it opens downward (nothing is docked to the bottom of the viewport),
 * anchors to the icon's left edge and grows right into the form, and clamps
 * to the viewport so it cannot leave the screen at any width. 1000 clears
 * the app chrome while staying under the calculator modals at 9999. */
.paintops-tooltip .paintops-tooltip-text {
  /* display: none, NOT visibility: hidden. A hidden-but-visible element
   * still takes part in layout, and this one is anchored to an icon and up
   * to 260px wide, so on a 390px phone the closed tooltip alone pushed
   * document.scrollWidth to 421 and the whole app scrolled sideways.
   * Measured, after the 2026-09-20 reposition introduced it. The cost is
   * the fade, which cannot animate from display: none; a tooltip that
   * appears instantly is a fair trade for a page that does not drift. */
  display: none;
  opacity: 1;
  position: absolute;
  top: calc(100% + 8px);
  bottom: auto;
  left: 0;
  transform: none;
  width: 260px;
  max-width: calc(100vw - 32px);
  background: var(--po-tooltip-panel, #23282d);
  color: #fff;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  padding: 8px 10px;
  border-radius: var(--po-radius-sm, 6px);
  z-index: 1000;
  pointer-events: none;
}

.paintops-tooltip:hover .paintops-tooltip-text,
.paintops-tooltip:focus .paintops-tooltip-text {
  display: block;
}

/* An optional bold first line inside a tooltip (2026-09-20), so a tooltip
 * carrying a paragraph has a name on it. Block, so no <br> is needed in the
 * PHP that builds it. */
.paintops-tooltip-title {
  display: block;
  margin-bottom: 4px;
  font-weight: 700;
  color: #fff;
}

/* On a phone the bubble is PINNED TO THE SCREEN, not to its icon.
 *
 * Anchoring to the icon cannot work at this width and the numbers say so.
 * Measured across all seven tooltips in the app at 390px, the icons land
 * between x=62 and x=220, because each one follows a label of a different
 * length. Clamping the width to what fits beside the worst of them leaves
 * 154px, which turns a two-sentence note into eight lines. Any fixed width
 * large enough to read runs off the right edge of the narrow cases.
 *
 * So below 600px it stops being anchored and becomes a small sheet at the
 * bottom of the screen: both gutters set, so it is correct for every icon
 * at every width by construction, and out of the way of the field you were
 * looking at when you tapped.
 *
 * NOT left/right pinning with top: auto, which was the first attempt. With
 * both top and bottom auto a fixed box takes its static position, and for
 * an inline child of a 16px inline-flex circle that lands CENTERED ON the
 * icon: measured at tipTop 775 against an icon spanning 792 to 808, so the
 * bubble sat over the label it belongs to. There is no CSS expression for
 * "just under that icon" once the box is fixed, and the static position
 * shifts with the bubble's own height, so bottom-pinning is the honest
 * answer rather than a magic offset that holds for one string length. */
@media (max-width: 600px) {
  .paintops-tooltip .paintops-tooltip-text {
    position: fixed;
    left: 16px;
    right: 16px;
    bottom: 16px;
    top: auto;
    width: auto;
    max-width: none;
    padding: 12px 14px;
    font-size: 13px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, .28);
  }
}

/* Status pills (Quote / Estimate / Bid / Job), replaces a <select> with
 * tappable buttons, faster to use on a phone than opening a dropdown. */
.paintops-status-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 1rem;
}

.paintops-status-pill {
  flex: 1 1 auto;
  min-width: 90px;
  text-align: center;
  padding: 10px 14px;
  border: 1px solid var(--po-input-border, #ccc);
  border-radius: var(--po-radius-sm, 6px);
  background: var(--po-card-bg, #fff);
  color: var(--po-text, #333);
  font-weight: 600;
  cursor: pointer;
  user-select: none;
}

.paintops-status-pill:hover {
  background: var(--po-hover, #f2f6fa);
}

.paintops-status-pill.active {
  background: var(--po-accent, #4682B4);
  border-color: var(--po-accent, #4682B4);
  color: #fff;
}

/* Sticky Save bar, keeps the primary action reachable without
 * scrolling back up on a LONG form (New Job/Quote, New Quote/Estimate).
 * Don't use this on short forms that already fit on one screen, the
 * bordered/shadowed bar reads as an empty box with nothing in it next
 * to a small button, since there's no real scrolling for it to help
 * with. Use .paintops-form-save (below) there instead.
 *
 * position: fixed rather than position: sticky, sticky only works if
 * every single ancestor between this element and the viewport has
 * overflow: visible, and WP themes very commonly set overflow-x:
 * hidden somewhere in the page wrapper (to stop mobile horizontal
 * scroll), which silently breaks sticky with no error anywhere. Fixed
 * is anchored to the viewport itself, so it can't be broken by a
 * theme's wrapper markup. .job-form's padding-bottom below reserves
 * room so this bar never overlaps the last field on screen.
 */
.paintops-sticky-save {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 100;
  background: var(--po-card-bg, #fff);
  border-top: 1px solid var(--po-card-border, #ddd);
  padding: 14px 20px;
  text-align: center;
  box-shadow: 0 -2px 10px rgba(0,0,0,.08);
}

/* When the mobile bottom nav is active, the fixed Save bar stacks ABOVE
 * the nav. Its CONTAINER is anchored flush to the nav's top edge
 * (bottom = --pbn-bar, the nav's own height) and the anti-mis-tap
 * clearance between the Save button and the raised "+" is carried as
 * opaque padding INSIDE the container (--pbn-clear minus --pbn-bar,
 * plus the bar's normal 14px), so no page content ever shows in a gap
 * between the two bars while scrolling (Mike's ask, 2026-07-19). The
 * button's on-screen position is unchanged. Both vars are defined on
 * body.paintops-nav-bottom-active by the nav (manage-jobs.php); the
 * fallbacks keep this safe on its own and compute the same numbers. */
@media screen and (max-width: 782px) {
  body.paintops-nav-bottom-active .paintops-sticky-save {
    bottom: var(--pbn-bar, calc(64px + env(safe-area-inset-bottom)));
    padding-bottom: calc(14px + var(--pbn-clear, calc(100px + env(safe-area-inset-bottom))) - var(--pbn-bar, calc(64px + env(safe-area-inset-bottom))));
    box-shadow: 0 -2px 10px rgba(0,0,0,.14);
  }
  body.paintops-nav-bottom-active .job-form {
    padding-bottom: calc(var(--pbn-clear, 100px) + 96px);
  }
}

/* Plain save-button spacing for short forms (New Customer, etc.),
 * just breathing room, no bar/border/shadow/fixed positioning. */
.paintops-form-save {
  margin-top: 28px;
  margin-bottom: 40px;
}

/* Job Financials page: each section is its own full-size .cs-financial-box
 * (no outer wrapper box), stacked with a little gap, and the Save
 * Financials button uses the same fixed .paintops-sticky-save bar as the
 * job form, so this reserves room at the bottom so that bar never
 * overlaps the last box (Invoices / Receipts). */
.paintops-financials-page { padding-bottom: 90px; }
.paintops-financials-page > .cs-financial-box,
.paintops-financials-page > form > .cs-financial-box { margin-bottom: 16px; }

/* Job Financials page accordions (2026-07-22, Mike): every data section
 * (Gross, Payments, Hours, Invoices, Expenses) collapses to a one-line
 * summary with its saved totals on the right, same idea as the admin
 * settings accordions, so the page reads as a dashboard at a glance. The
 * Job Financials tiles and Estimate vs Actual stay open boxes above. */
details.cs-fin-acc {
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius, 8px);
  background: var(--po-card-bg, #fff);
  box-shadow: var(--po-card-shadow, 0 1px 2px rgba(0,0,0,.04));
  margin: 16px 0;
}
details.cs-fin-acc > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px 14px;
  flex-wrap: wrap;
  padding: 14px 18px;
  font-weight: 600;
  font-size: 16px;
  color: var(--po-heading, #2f5aae);
}
details.cs-fin-acc > summary::-webkit-details-marker { display: none; }
details.cs-fin-acc > summary::before {
  content: "\25B8";
  font-size: 13px;
  line-height: 1;
  color: var(--po-text-muted, #888);
  transition: transform 0.15s ease;
}
details.cs-fin-acc[open] > summary::before { transform: rotate(90deg); }
details.cs-fin-acc > summary .cs-fin-acc-meta {
  margin-left: auto;
  font-size: 13px;
  font-weight: 500;
  color: var(--po-text-muted, #777);
  text-align: right;
}
.cs-fin-acc-meta .pos { color: var(--po-positive, #1e7e34); font-weight: 600; }
.cs-fin-acc-meta .neg { color: var(--po-negative, #c0392b); font-weight: 600; }
.cs-fin-acc-meta .warn { color: #8a6d3b; font-weight: 600; }
/* Overpaid (2026-09-28). Its own color because it is its own state: not the
 * green of money collected and not the red of money missing, but money in
 * hand that has not been accounted for yet. Same amber the accordion meta
 * already uses for a warning, so the status line and the section header
 * beside it agree. */
.cs-fin-warn { color: #8a6d3b; }
details.cs-fin-acc > .cs-fin-acc-body { padding: 2px 18px 18px; }
/* Sections that render their own .cs-financial-box inside an accordion
 * (Invoices, Expenses) shed the inner card chrome so it doesn't double. */
details.cs-fin-acc .cs-financial-box {
  border: none !important;
  box-shadow: none !important;
  background: transparent;
  margin: 0 !important;
  padding: 0;
}

/* Business page restructure (2026-09-06): global year switch, summary
 * tiles, and flat always-visible sections (the accordion CSS above still
 * serves the Job Financials page). These classes are unused in the free
 * build (the Business page is premium-only). */
.po-biz-head { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin: 6px 0 4px; }
.po-biz-yearswitch { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.po-biz-yearswitch-label { font-size: 13.5px; font-weight: 600; color: var(--po-text-muted, #6b7684); }
.po-biz-yeartabs { display: flex; background: var(--po-card-bg, #fff); border: 1px solid var(--po-card-border, #e3e8ee); border-radius: 9px; padding: 3px; gap: 2px; flex-wrap: wrap; }
.po-biz-yeartab { padding: 7px 15px; border-radius: 7px; font-size: 14px; font-weight: 600; color: var(--po-text-muted, #6b7684); text-decoration: none; }
.po-biz-yeartab:hover { color: var(--po-heading, #2f5aae); }
.po-biz-yeartab.is-active { background: var(--po-accent, #3D5A80); color: #fff; font-weight: 800; }
.po-biz-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; margin: 14px 0 24px; }
.po-biz-tile { background: var(--po-card-bg, #fff); border: 1px solid var(--po-card-border, #e3e8ee); border-radius: var(--po-radius, 12px); padding: 16px 18px; }
.po-biz-tile-label { font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--po-text-muted, #6b7684); }
/* 22px/600, not 23px/800: one figure weight across the app's stat surfaces
 * (job tiles 20px, these page-level tiles 22px, the profit hero 30px), and
 * tabular so the four tiles align down their digits. */
.po-biz-tile-value { font-size: 22px; font-weight: 600; letter-spacing: -.01em; color: var(--po-heading, #293241); margin-top: var(--po-s1, 4px); font-variant-numeric: tabular-nums; }
.po-biz-tile-sub { font-size: 12.5px; color: var(--po-text-muted, #6b7684); margin-top: 2px; }
.po-biz-tile.is-total { background: var(--po-heading, #293241); border-color: var(--po-heading, #293241); }
.po-biz-tile.is-total .po-biz-tile-label,
.po-biz-tile.is-total .po-biz-tile-sub { color: rgba(255,255,255,0.72); }
.po-biz-tile.is-total .po-biz-tile-value { color: #fff; }
.cs-fin-section { border: 1px solid var(--po-card-border, #e3e8ee); border-radius: var(--po-radius, 12px); background: var(--po-card-bg, #fff); box-shadow: var(--po-card-shadow, 0 1px 2px rgba(0,0,0,.04)); margin: 16px 0; padding: 20px 22px; }
.cs-fin-section-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin: 0 0 12px; }

/* THE BUSINESS PAGE SECTIONS ARE ACCORDIONS, ONE OPEN AT A TIME (Mike
 * 2026-09-20). They are <details> with a shared name=, which makes modern
 * browsers enforce the "one open" rule on their own; business-accordion.js
 * does the same for the ones that do not support it yet. The section keeps
 * its card look, so an open accordion and the old always-open card are the
 * same object. */
details.po-biz-acc { padding: 0; overflow: hidden; }
details.po-biz-acc > summary.cs-fin-section-head {
  margin: 0; padding: 18px 22px; cursor: pointer; list-style: none;
  align-items: center;
}
details.po-biz-acc > summary.cs-fin-section-head::-webkit-details-marker { display: none; }
details.po-biz-acc > summary.cs-fin-section-head::after {
  content: ''; flex: 0 0 auto; width: 8px; height: 8px; margin-left: 4px;
  border-right: 2px solid var(--po-text-soft, #97a2ae);
  border-bottom: 2px solid var(--po-text-soft, #97a2ae);
  transform: rotate(45deg) translateY(-2px);
  transition: transform .15s ease;
}
details.po-biz-acc[open] > summary.cs-fin-section-head::after { transform: rotate(-135deg) translateY(0); }
details.po-biz-acc > summary.cs-fin-section-head:hover h2 { color: var(--po-accent, #437bab); }
details.po-biz-acc > summary.cs-fin-section-head:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: -2px; }
/* The meta pushes to the right on a wide screen and drops under the heading
 * on a narrow one, where a right-aligned figure beside a wrapped title reads
 * as belonging to the wrong row. */
details.po-biz-acc > summary.cs-fin-section-head .cs-fin-section-meta.ml { margin-left: auto; }
@media (max-width: 620px) {
  details.po-biz-acc > summary.cs-fin-section-head { padding: 14px 16px; }
  details.po-biz-acc > summary.cs-fin-section-head .cs-fin-section-meta.ml { margin-left: 0; width: 100%; text-align: left; }
}
details.po-biz-acc > *:not(summary) { padding: 0 22px 20px; }
@media (max-width: 620px) { details.po-biz-acc > *:not(summary) { padding: 0 16px 16px; } }
/* The body wrappers already carry their own padding, so they would double it. */
details.po-biz-acc > .cs-fin-acc-body { padding: 0 22px 20px; }
@media (max-width: 620px) { details.po-biz-acc > .cs-fin-acc-body { padding: 0 16px 16px; } }

/* THE VENDORS & 1099 TABLE ON A PHONE (Mike 2026-09-20).
 *
 * It was a 7-column table with min-width:860px inside a horizontal scroller,
 * so on a phone it was a sideways drag; and whatever the host theme does to
 * narrow tables was stacking the cells anyway, leaving a header row sitting
 * over data that was no longer beneath it and money cells that looked empty.
 *
 * Below 760px each row becomes a card and every cell prints its own label
 * from data-label, so nothing depends on a header row being alongside. The
 * rule sets display itself rather than trying to out-specify the theme, which
 * is what makes it hold wherever it renders. A container query is paired with
 * the viewport one so the same thing happens inside a narrow theme column on
 * a wide screen. */
/* Outstanding Balances renders its own heading inside its body, because the
 * Profits page shares that renderer. Inside this accordion the summary above
 * already says it, so the inner one is hidden here rather than removed from
 * the renderer, which would strip it off Profits too. */
.po-biz-acc-ob > .cs-fin-acc-body > .job-form-section:first-child { display: none; }
/* Request Performance accordion on the Business page (Mike 2026-09-20). The
 * card inside is the same one the ?requestPerf=1 page uses, so it needs no
 * treatment of its own beyond dropping the outer box's doubled border. */
.po-biz-acc-rp > .cs-fin-acc-body > .cs-financial-box {
  border: 0;
  box-shadow: none;
  padding: 0;
  margin: 0;
}
.po-biz-rp-more {
  margin: 12px 0 0;
  font-size: var(--po-fs-meta, 13px);
}

.po-vend-wrap { overflow-x: auto; }
.po-vend-table { min-width: 860px; }

@media (max-width: 760px) {
  .po-vend-wrap { overflow-x: visible; }
  .po-vend-table { min-width: 0; width: 100%; display: block; }
  .po-vend-table thead { display: none; }
  .po-vend-table tbody, .po-vend-table tr, .po-vend-table td { display: block; width: auto; }
  .po-vend-table tr {
    border: 1px solid var(--po-card-border, #ddd);
    border-radius: var(--po-radius-sm, 6px);
    background: var(--po-card-bg, #fff);
    padding: 10px 12px; margin: 0 0 10px;
  }
  .po-vend-table td {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 5px 0 !important; border: none !important;
    text-align: right !important; white-space: normal !important;
  }
  .po-vend-table td::before {
    content: attr(data-label);
    flex: 0 0 auto; text-align: left;
    font-size: var(--po-fs-label, 11px); font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase;
    color: var(--po-text-muted, #5b6673);
  }
  /* The vendor name is the card's heading, so it takes the full width and
     drops its label: a card that opens with the word VENDOR over the name
     is labelling the obvious. */
  .po-vend-table td[data-label="Vendor"] { display: block; text-align: left !important; }
  .po-vend-table td[data-label="Vendor"]::before { display: none; }
  /* box-sizing, or the 100% is 100% PLUS the theme's padding and border and
     the input hangs off the card's right edge. */
  .po-vend-table td[data-label="Vendor"] input[type="text"] { width: 100% !important; box-sizing: border-box; font-weight: 600; }
  /* widefat's zebra striping painted a band behind each card in this layout,
     which read as a second, misaligned box. The card carries the surface now.
     NOT !important, deliberately: the "undecided and over the threshold" rows
     carry their highlight as an inline style, and inline beats a normal
     author rule, so the one piece of colour that means something survives. */
  .po-vend-table.striped > tbody > tr:nth-child(odd),
  .po-vend-table.striped > tbody > tr:nth-child(even) { background: var(--po-card-bg, #fff); }
  .po-vend-table td { background: transparent !important; }
  /* The two decisions are what you came to the card to set, so they get the
     room. Everything above them is the evidence. */
  .po-vend-table td[data-label="1099"] select,
  .po-vend-table td[data-label="Same as"] select { width: 100% !important; max-width: 60%; min-height: 40px; }
  .po-vend-table td[data-label="Total"] { border-top: 1px solid var(--po-line, #eef1f4) !important; margin-top: 4px; padding-top: 8px !important; }
}
@container (max-width: 760px) {
  .po-vend-wrap { overflow-x: visible; }
  .po-vend-table { min-width: 0; width: 100%; display: block; }
  .po-vend-table thead { display: none; }
  .po-vend-table tbody, .po-vend-table tr, .po-vend-table td { display: block; width: auto; }
  .po-vend-table tr {
    border: 1px solid var(--po-card-border, #ddd);
    border-radius: var(--po-radius-sm, 6px);
    padding: 10px 12px; margin: 0 0 10px;
  }
  .po-vend-table td {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 5px 0 !important; border: none !important;
    text-align: right !important; white-space: normal !important;
  }
  .po-vend-table td::before {
    content: attr(data-label); flex: 0 0 auto; text-align: left;
    font-size: var(--po-fs-label, 11px); font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase;
    color: var(--po-text-muted, #5b6673);
  }
  .po-vend-table td[data-label="Vendor"] { display: block; text-align: left !important; }
  .po-vend-table td[data-label="Vendor"]::before { display: none; }
  /* box-sizing, or the 100% is 100% PLUS the theme's padding and border and
     the input hangs off the card's right edge. */
  .po-vend-table td[data-label="Vendor"] input[type="text"] { width: 100% !important; box-sizing: border-box; font-weight: 600; }
  /* widefat's zebra striping painted a band behind each card in this layout,
     which read as a second, misaligned box. The card carries the surface now.
     NOT !important, deliberately: the "undecided and over the threshold" rows
     carry their highlight as an inline style, and inline beats a normal
     author rule, so the one piece of colour that means something survives. */
  .po-vend-table.striped > tbody > tr:nth-child(odd),
  .po-vend-table.striped > tbody > tr:nth-child(even) { background: var(--po-card-bg, #fff); }
  .po-vend-table td { background: transparent !important; }
  .po-vend-table td[data-label="1099"] select,
  .po-vend-table td[data-label="Same as"] select { width: 100% !important; max-width: 60%; min-height: 40px; }
}
@media (max-width: 620px) { details.po-biz-acc > .cs-fin-acc-body { padding: 0 16px 16px; } }
.cs-fin-section-head h2 { margin: 0; font-size: 16px; font-weight: 800; color: var(--po-heading, #293241); }
.cs-fin-section-meta { font-size: 13px; font-weight: 500; color: var(--po-text-muted, #6b7684); }
.cs-fin-section-meta.ml { margin-left: auto; text-align: right; }
@media (max-width: 820px) { .po-biz-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .po-biz-tiles { grid-template-columns: 1fr; } }


.job-form label {
  display: block;
  font-weight: 600;
  font-size: 15px;
  margin-bottom: 0.6rem;
  text-align: left;
}

.job-form input,
.job-form select,
.job-form textarea {
  width: 100% !important;
  padding: 0.5rem !important;
  margin-bottom: 2rem !important;
  border: 1px solid var(--po-input-border, #ccc) !important;
  border-radius: var(--po-radius-xs, 4px) !important;
  box-sizing: border-box !important;
}

/* Bigger, easier-to-hit typed fields for on-the-job phone use. 16px is the
 * threshold below which iOS auto-zooms the page the moment a field is
 * focused (jarring, and it leaves the crew scrolled and pinching back out),
 * so every typed field sits at 16px; min-height keeps each one a comfortable
 * thumb target and the extra padding gives the text room. Checkboxes and
 * radios are deliberately excluded (they keep their own 22px sizing below),
 * which is why this lists the typed input types instead of a bare
 * ".job-form input". */
.job-form input[type="text"],
.job-form input[type="email"],
.job-form input[type="tel"],
.job-form input[type="number"],
.job-form input[type="date"],
.job-form input[type="password"],
.job-form input[type="search"],
.job-form input[type="url"],
.job-form input:not([type]),
.job-form select,
.job-form textarea {
  font-size: 16px !important;
  min-height: 44px;
  padding: 0.7rem 0.75rem !important;
  line-height: 1.4;
}
.job-form textarea { min-height: 96px; }

/* Validation errors after a submit. Rendered as a prominent banner (not the
 * old bare red text) and, via a small script where it's echoed, scrolled
 * into view on load, so a failed save is never mistaken for a successful one
 * when the fixed Save bar sits over an off-screen error. */
.paintops-form-errors {
  margin: 0 0 20px;
  padding: 14px 16px;
  border: 1px solid #e6b3b0;
  border-radius: var(--po-radius-xs, 4px);
  background: #fdecea;
  color: #9b1c17;
  font-size: 15px;
  line-height: 1.5;
}
.paintops-form-errors:focus { outline: none; }

/* Chromium on Windows draws a <select>'s selected-option text using its
 * own native OS chrome and ignores left/right padding on it completely,
 * this (not caching, not specificity) is why Job Category/Job
 * Classification never got any visible left inset even with !important
 * above; every OTHER field type (text inputs, textareas) doesn't have
 * this problem. Turning off the native appearance and drawing a plain
 * arrow ourselves hands the whole box back to normal CSS, so the padding
 * finally has something to apply to. */
.job-form select {
  appearance: none !important;
  -webkit-appearance: none !important;
  -moz-appearance: none !important;
  background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%23666' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 12px 8px;
  padding-right: 2.25rem !important;
}

/* Checkboxes/radios were inheriting the text-input rule just above,
 * width:100% stretched a checkbox to fill its container, which pushed its
 * label text onto its own line instead of sitting beside it (the "Work
 * Areas" Interior/Exterior checkboxes stacking oddly instead of sitting in
 * a row). Every checkbox/radio anywhere inside a .job-form is excluded
 * from that rule and sized on its own below instead, width/height are
 * deliberately NOT set here (even though this selector is more specific
 * than the app-wide input[type="checkbox"] rule below): setting them here
 * would win the cascade and silently override that rule's 22px, which is
 * exactly the bug that shrank these back down last time. The text-input
 * rule above now uses !important (to force real form fields to keep their
 * padding regardless of theme CSS), so every property here needs
 * !important too, or the !important above would win instead. */
.job-form input[type="checkbox"],
.job-form input[type="radio"] {
  padding: 0 !important;
  margin-bottom: 0 !important;
  border: none !important;
  border-radius: 0 !important;
}

/* App-wide checkbox sizing, the browser default (~13px) is a small,
 * easy-to-miss target, especially on a phone out on a job site. Sized
 * clearly larger than a line of body text, and applied everywhere
 * job-style.php loads (every app page), not just inside .job-form, so
 * Scope Components, Situational Clauses, and every other checklist in the
 * app gets the same bigger, consistent checkbox. accent-color re-themes
 * the native checkbox (check color/fill) without giving up native
 * behavior or accessibility the way a fully custom checkbox would.
 * !important on width/height only, since a couple of one-off checkboxes
 * elsewhere (e.g. the itemize-quote toggle) carry their own inline
 * width/height that would otherwise win by specificity and drift out of
 * sync with this app-wide size. */
input[type="checkbox"] {
  width: 22px !important;
  height: 22px !important;
  accent-color: var(--po-accent, #4682B4);
  cursor: pointer;
  flex-shrink: 0;
  vertical-align: middle;
}

.job-form button {
  background: var(--po-accent, #4682B4);
  color: #fff;
  padding: 0.7rem 1.2rem;
  min-height: 44px;
  font-size: 15px;
  border: none;
  border-radius: var(--po-radius-xs, 4px);
  cursor: pointer;
}

.job-form button:hover {
  opacity: 0.9;
}

/* ...EXCEPT THESE THREE, WHICH ARE NOT FORM BUTTONS. The Directions gear
 * and the two menu toggles are icon controls that drive JavaScript; they
 * are reset to bare text near the top of this file, by three SINGLE-CLASS
 * selectors. The rule above is one class plus one element, so wherever
 * these controls render inside a .job-form it outranks that reset and
 * repaints them as solid accent buttons. On ?editEid= the job actions bar
 * renders inside the form, which turned the Directions gear into a solid
 * 28x44 accent block: the glyph on top of it is ALSO accent (from
 * .pv-quick-actions .pv-dir-gear, two classes, which does win), so the
 * gear vanished into its own background (Mike 2026-09-15, "blanked out by
 * a solid color"). Restating the reset one notch higher, immediately after
 * the rule that breaks it.
 *
 * It is stated narrowly on purpose: every later rule in this file that
 * genuinely styles these controls (the tile face, the gear's accent ink
 * and 2px padding, the toggle's muted ink) is at least as specific AND
 * comes after, so all of it still applies. This only undoes the form
 * button paint. */
.job-form .pv-more-toggle,
.job-form .po-jrow-actions-toggle,
.job-form .pv-dir-gear {
  background: none;
  color: inherit;
  padding: 0;
  min-height: 0;
  border: 0;
  border-radius: 0;
  font: inherit;
}

/* Shared app-wide button look, for the buttons/links living OUTSIDE a
 * .job-form (page-level actions like "+ New Job/Quote", "+ New Customer",
 * "New Quote", "Analyze Photo") that used to fall back to WordPress's
 * plain default .button/.button-primary admin styling, which looks like
 * a wp-admin screen, not the rest of this app. Matches the same solid-
 * accent-color/white-text "primary" look as .job-form button above, plus
 * a bordered white "secondary" for a lower-emphasis action sitting next
 * to a primary one (Search next to +New Customer, Take/Choose Photo next
 * to Analyze Photo, etc.). */
/* !important throughout this whole group: a plain "any <button> inside
 * .job-form" element-selector rule above (.job-form button) has higher
 * specificity than a single class like .paintops-btn-secondary, so
 * without !important it silently won and painted every button (Take
 * Photo, Choose Photo, Analyze Photo, etc.) the same solid accent color
 * regardless of which variant class was actually on it. */
/* (2026-09-13 visual pass) Three levels of emphasis, not two shades of the
 * same one. PRIMARY is solid accent and there should be exactly one per
 * screen. SECONDARY is a bordered white button in the TEXT color, not the
 * accent: it used to be accent-on-white, which read as important as the
 * primary and left a screen with six equally loud actions. TERTIARY is the
 * plain link. Desktop drops to 38px because a 44px control row looks like a
 * phone app on a 27in monitor; phones keep the full 44px touch target in the
 * media query below, since this is used on ladders. */
.paintops-btn {
  display: inline-block;
  padding: 9px 16px !important;
  min-height: 38px;
  border-radius: var(--po-radius-xs, 4px) !important;
  font-weight: 600;
  font-size: var(--po-fs-body, 14px);
  text-decoration: none;
  cursor: pointer;
  border: 1px solid transparent !important;
  transition: background-color .12s ease, border-color .12s ease;
  line-height: 1.45;
}
@media (max-width: 782px) {
  .paintops-btn { min-height: 44px; padding: 11px 18px !important; font-size: 15px; }
}

.paintops-btn:hover {
  opacity: 1;
}

/* --po-on-accent is the INK that sits on the accent colour, defaulting to
 * white. It exists because a theme with a light accent (a dark theme's link
 * blue, a gold, an amber) cannot carry white button text at 4.5:1, and no
 * single accent value satisfies both "readable as a link on the card" and
 * "readable behind white text". The theme flips the ink instead. */
.paintops-btn-primary,
.paintops-btn-primary:visited {
  /* Fallbacks corrected from #4682B4 to #437bab (2026-09-14). The fallback
     governs on every shape-only surface (wp-admin, and a dark theme on the
     classic layout), and white on #4682B4 measures 4.11:1 while this button
     is 14px/600, which is NOT WCAG "large text", so 4.11 was a real AA miss.
     #437bab is the same value classic's --po-accent already carries and puts
     both halves at 4.51:1: white on the filled button, and the accent as text
     on a white card. Do not lighten it back. */
  background: var(--po-accent, #437bab) !important;
  color: var(--po-on-accent, #fff) !important;
  border-color: var(--po-accent, #437bab) !important;
}

.paintops-btn-primary:hover,
.paintops-btn-primary:focus-visible {
  filter: brightness(1.07);
  /* The ink is restated (the base rule already pins it with !important;
   * this says so at the hover state too, where a reader looking for it
   * would expect to find it). The BACKGROUND is deliberately left to the
   * base rule: two buttons in the app carry an inline background on this
   * class, and an !important here would flatten them. */
  color: var(--po-on-accent, #fff) !important;
}

/* The secondary's fill and border already carry !important on hover; the
 * ink is restated here so the same theme rule cannot reach it either. */
.paintops-btn-secondary:hover,
.paintops-btn-secondary:focus-visible {
  color: var(--po-text, #2b3440) !important;
}

.paintops-btn-secondary,
.paintops-btn-secondary:visited {
  background: var(--po-btn2-bg, #fff) !important;
  color: var(--po-text, #2b3440) !important;
  border-color: var(--po-input-border, #ccc) !important;
}
.paintops-btn-secondary:hover,
.paintops-btn-secondary:focus-visible {
  background: var(--po-hover, #f4f7fa) !important;
  border-color: var(--po-accent-line, rgba(61,90,128,.35)) !important;
}

/* One visible keyboard focus ring for everything focusable inside the app,
 * in the accent. There was none before beyond each browser's default, which
 * on a link is a 1px near-black hairline that disappears on a dark row. A
 * back office gets driven by keyboard: tab order, Enter, Escape. Scoped to
 * the app's own canvas and chrome so the host theme's pages are untouched.
 * :focus-visible, not :focus, so a mouse click never paints a ring. */
.paintops-app-canvas-inner a:focus-visible,
.paintops-app-canvas-inner button:focus-visible,
.paintops-app-canvas-inner input:focus-visible,
.paintops-app-canvas-inner select:focus-visible,
.paintops-app-canvas-inner textarea:focus-visible,
.paintops-app-canvas-inner summary:focus-visible,
.paintops-btn:focus-visible,
.button:focus-visible,
.job-form button:focus-visible,
.pv-more-toggle:focus-visible,
.po-jrow-actions-toggle:focus-visible,
.pv-dir-gear:focus-visible {
  outline: 2px solid var(--po-accent, #3D5A80);
  outline-offset: 2px;
  border-radius: var(--po-radius-xs, 4px);
}
/* On the navy rail the accent ring would disappear, so the ring there is
 * the light marker color instead. */
.paintops-shell .po-side a:focus-visible,
.paintops-shell .po-side button:focus-visible {
  outline: 2px solid var(--po-nav-marker, #98C1D9);
  outline-offset: -2px;
}

/* AI action buttons (2026-07-22, Mike's pick from the mockups: the "G2
 * steel tint"). One shared class marks every AI-powered action, Analyze
 * Receipt, the quote builder's feedback/rewrite/materials trio, Explain
 * Profit, Scan with AI, as one family: a soft steel-tinted field with a
 * sparkles icon, quiet enough to never outrank a Save/Create button.
 * The icon is drawn with a CSS mask filled by currentColor, so it
 * recolors with the text under every theme with no per-button SVG
 * markup. Theme overrides live in app-themes.php as --po-ai-* vars
 * (Dark defines its own; light themes use these defaults, the tint is
 * near-neutral enough to sit under any accent). !important mirrors the
 * .paintops-btn convention, theme/WP .button rules are more specific. */
.paintops-btn-ai {
  display: inline-flex !important;
  align-items: center;
  gap: 7px;
  background: var(--po-ai-bg, #f1f6fa) !important;
  color: var(--po-ai-text, #3a6a94) !important;
  border: 1px solid var(--po-ai-border, #c3d4e2) !important;
  border-radius: var(--po-radius-xs, 4px) !important;
  font-weight: 600;
}
.paintops-btn-ai:hover,
.paintops-btn-ai:focus {
  background: var(--po-ai-bg-hover, #e5eff7) !important;
  color: var(--po-ai-text, #3a6a94) !important;
}
/* An AI button is disabled ONLY while a request is in flight: not one of
 * them starts disabled in markup, and all four handlers disable on click
 * and re-enable on the response. So :disabled here means "working", and
 * it should look busy rather than dead.
 *
 * .55 was the bug Mike hit (2026-09-15): the whole button greyed out, the
 * label change to "Thinking..." was easy to miss on a long quote screen,
 * and there was no way to tell a click had registered from a click that
 * had not. Now the button stays legible and the sparkle twinkles, which
 * is motion the eye catches without being asked to read anything.
 *
 * .is-busy is here for any future handler that keeps its button clickable
 * while it works. */
.paintops-btn-ai:disabled { opacity: .9; cursor: progress; }
.paintops-btn-ai:disabled::before,
.paintops-btn-ai.is-busy::before {
  animation: po-ai-twinkle 1.15s ease-in-out infinite;
}
@keyframes po-ai-twinkle {
  0%, 100% { opacity: 1;   transform: scale(1)    rotate(0deg); }
  50%      { opacity: .42; transform: scale(.78) rotate(-14deg); }
}
@media (prefers-reduced-motion: reduce) {
  /* No pulse, but the state still has to be visible: hold the icon at a
     steady dimmed value so it differs from the resting button. */
  .paintops-btn-ai:disabled::before,
  .paintops-btn-ai.is-busy::before { animation: none; opacity: .5; }
}
.paintops-btn-ai::before {
  content: "";
  flex: 0 0 15px;
  width: 15px;
  height: 15px;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9.6 5.6 11 2l1.4 3.6L16 7l-3.6 1.4L11 12 9.6 8.4 6 7l3.6-1.4Zm9 7.4.9-2.3.9 2.3 2.3.9-2.3.9-.9 2.3-.9-2.3-2.3-.9 2.3-.9ZM5.5 14l.9 2.3 2.3.9-2.3.9-.9 2.3-.9-2.3-2.3-.9 2.3-.9.9-2.3Z'/%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'%3E%3Cpath d='M9.6 5.6 11 2l1.4 3.6L16 7l-3.6 1.4L11 12 9.6 8.4 6 7l3.6-1.4Zm9 7.4.9-2.3.9 2.3 2.3.9-2.3.9-.9 2.3-.9-2.3-2.3-.9 2.3-.9ZM5.5 14l.9 2.3 2.3.9-2.3.9-.9 2.3-.9-2.3-2.3-.9 2.3-.9.9-2.3Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* "Back to X" navigation link, sits above a sub-page's content (Job
 * Financials, Add Photos, Add Expense, Customer detail, Schedule Follow-
 * Up, etc.) as a bordered pill button instead of a bare "&larr; Back to"
 * text link, with extra top margin so it reads as clearly separate from
 * the main nav above it rather than crowding right up against it. */
.paintops-back-link,
.paintops-back-link:visited {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 28px 0 14px;
  padding: 0.5rem 0.9rem !important;
  background: var(--po-btn2-bg, #fff) !important;
  border: 1px solid var(--po-input-border, #ccc) !important;
  border-radius: var(--po-radius-xs, 4px) !important;
  /* Fallback corrected from #4682B4 (4.11:1 on the white button face) to
     the same #437bab classic's --po-accent uses. 2026-09-14. */
  color: var(--po-accent, #437bab) !important;
  font-weight: 600;
  font-size: 14px;
  text-decoration: none;
}

.paintops-back-link:hover {
  background: var(--po-hover, #f7f7f7) !important;
}

.paintops-back-link .dashicons {
  font-size: 16px;
  width: 16px;
  height: 16px;
}

/* Hidden Content Box */
.hidden-box {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease-out, padding 0.3s ease-out;
}

.hidden-box.show {
  max-height: 7500px; /* adjust for content size */
  padding: 0.5rem 0;
}

/* Customer/Address/Scheduled glance row, originally just the Job
 * View page's own at-a-glance card, moved here (from a local <style>
 * block on that one page) so paintops_render_job_customer_summary()
 * can reuse it on every job sub-page (Financials, Add Expense, Photos,
 * Create Quote, Schedule Follow-Up), all of which load job-style.php
 * but never render job-view.php's own inline styles. */
.pv-glance-row { padding:4px 0; font-size:15px; }
.pv-glance-row strong { display:inline-block; min-width:110px; }

/* Inline glance variant (2026-07-19, Mike's tidy-the-top pass): the
 * Customer/Scheduled summary on job sub-pages sits on ONE line when the
 * width allows and wraps to stacked lines when it doesn't (flex-wrap,
 * the modern take on his two-float-divs idea). The label min-width is
 * dropped inside this variant so segments hug their content. */
.pv-glance-inline { display:flex; flex-wrap:wrap; gap:2px 36px; align-items:baseline; }
.pv-glance-inline .pv-glance-row { padding:2px 0; white-space:nowrap; }
.pv-glance-inline .pv-glance-row strong { min-width:0; margin-right:6px; }

/* Assigned team members in the "Workers:" glance row, with opt-in
   Call/Text links (job-view.php). Links only render when a teammate has
   saved a phone under PaintClarity > Painters/Helpers. */
/* Workers glance row: each assigned worker is its OWN bordered capsule
 * (Mike 2026-07-22: plain names ran together and were hard to tell
 * apart), with the name, the YOU tag, and any Call/Text mini-pills
 * grouped inside one chip, distinct boundaries, still one compact row.
 * pv-team-nameonly (set server-side) evens the right padding on a chip
 * with no inner pills. */
.pv-team-member { display:inline-flex; align-items:center; gap:7px; margin:2px 8px 4px 0; padding:3px 6px 3px 11px; border:1px solid var(--po-card-border,#d0d5dd); border-radius:999px; background:var(--po-tile-bg,#fafbfc); white-space:nowrap; vertical-align:middle; }
.pv-team-member.pv-team-nameonly { padding-right:11px; }
.pv-team-name { font-weight:600; font-size:13px; }
.pv-team-contact { display:inline-block; font-size:12px; font-weight:600; line-height:1.7; text-decoration:none; padding:0 9px; border-radius:999px; border:1px solid var(--po-border,#d0d5dd); background:var(--po-card-bg,#fff); color:var(--po-accent,#2271b1); }
.pv-team-contact:hover, .pv-team-contact:focus { background:var(--po-accent,#2271b1); color:#fff; }
.pv-team-you { display:inline-block; font-size:11px; font-weight:700; line-height:1.7; padding:0 9px; border-radius:999px; background:var(--po-accent-soft,rgba(70,130,180,0.12)); color:var(--po-accent,#2271b1); text-transform:uppercase; letter-spacing:.3px; }
@media (max-width:600px) { .pv-team-member { display:flex; justify-content:space-between; gap:10px; margin:8px 0; } .pv-team-contact { padding:3px 16px; } }

/* Customer name as a two-click contact reveal (Mike 2026-07-22): dotted
 * underline signals it's tappable; the Call/Text pills only appear after
 * the name is clicked, so a pocket tap can't dial the customer. */
.pv-cust-toggle { cursor:pointer; text-decoration:underline dotted; text-underline-offset:3px; }
.pv-cust-toggle .pv-more-chevron { font-size:11px; color:var(--po-text-muted,#888); }

/* Job status chip in the glance box: upcoming / mid-run / past / done. */
.pv-status-chip { display:inline-block; font-size:12px; font-weight:700; line-height:1.9; padding:0 11px; border-radius:999px; }
.pv-status-upcoming { background:var(--po-accent-soft,rgba(70,130,180,0.12)); color:var(--po-accent,#2271b1); }
.pv-status-active { background:rgba(30,126,52,0.12); color:var(--po-positive,#1e7e34); }
.pv-status-done { background:var(--po-tile-bg,#f0f0f1); color:var(--po-text-muted,#666); border:1px solid var(--po-card-border,#ddd); }
.pv-status-past { background:#fff8ec; color:#8a5a00; border:1px solid #e0c36a; }

/* Job Pulse, beside the status chip (2026-09-16). A quiet text link
 * rather than a second chip: the chip states a fact about the job, this
 * goes somewhere, and two pills side by side read as two statuses. */
.pv-pulse-link {
    margin-left: 10px;
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    color: var(--po-accent, #2271b1);
    border-bottom: 1px solid transparent;
}
.pv-pulse-link:hover, .pv-pulse-link:focus { border-bottom-color: currentColor; }

/* Job View (?viewEid=X) quick actions row + "More Actions" panel.
 * Referenced by job-view.php and estimates.php's quote-view page, but
 * never actually defined anywhere, these links were rendering as bare
 * unstyled text before this rule was added. */
.pv-quick-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 20px;
  margin: 10px 0 14px;
}

/* .pv-more-toggle and .pv-dir-gear inside this row are <button> since the
 * 2026-09-13 audit (B5), so this rule can no longer key off the tag alone:
 * name them explicitly or they fall back to the button reset near the top
 * of this file and lose the size, weight and accent color the links had. */
.pv-quick-actions a,
.pv-quick-actions .pv-more-toggle,
.pv-quick-actions .pv-dir-gear {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--po-accent, #4682B4);
  font-weight: 600;
  font-size: 15px;
  text-decoration: none;
}

.pv-quick-actions a:hover,
.pv-quick-actions .pv-more-toggle:hover,
.pv-quick-actions .pv-dir-gear:hover {
  text-decoration: underline;
}

.pv-quick-actions .dashicons {
  font-size: 18px;
  width: 18px;
  height: 18px;
}

.pv-dir-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.pv-dir-gear {
  margin-left: 4px;
  opacity: .65;
}
.pv-dir-gear:hover { opacity: 1; }
.pv-dir-gear .dashicons { font-size: 16px !important; width: 16px !important; height: 16px !important; }
.pv-dir-menu {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 60;
  min-width: 172px;
  margin-top: 6px;
  padding: 6px;
  background: var(--po-card-bg, #fff);
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius, 8px);
  box-shadow: 0 6px 20px rgba(0,0,0,.14);
}
.pv-dir-menu.show { display: block; }
.pv-dir-menu a {
  display: flex !important;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  border-radius: 6px;
  color: var(--po-text, #333) !important;
  font-weight: 400 !important;
  font-size: 14px !important;
  white-space: nowrap;
  text-decoration: none;
}
.pv-dir-menu a:hover { background: var(--po-tile-bg, #f2f5fb); text-decoration: none; }
.pv-dir-menu a.is-current { font-weight: 600 !important; }
.pv-dir-check { font-size: 16px !important; width: 16px !important; height: 16px !important; color: #1a7a43; }

.pv-more-toggle {
  color: var(--po-text-muted, #555) !important;
}

.pv-more-toggle .pv-more-chevron {
  font-size: 10px;
}

.pv-more-actions {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius, 8px);
  /* The CARD surface, not the tile tint (2026-09-15). On Seaside the tile
     tint (#f2fbfd) and the hover tint (#eef7fb) are all but the same color,
     so a hovered row in this menu was indistinguishable from a resting one:
     the menu simply did not respond to the mouse. A dropdown belongs on the
     plain card surface anyway, which is what every hover tint is designed
     to be seen against. */
  background: var(--po-card-bg, #fff);
  margin-bottom: 20px;
  /* Compact dropdown rather than a full-width block (Mike 2026-09-12): cap the
     width and lift it with a shadow so it reads as a menu under the toggle. On
     phones it just fills the available width. */
  max-width: 300px;
  box-shadow: 0 6px 18px rgba(16, 24, 40, .10);
}
@media (max-width: 340px) { .pv-more-actions { max-width: 100%; } }

/* A row in a More Actions panel. .pv-more-item is the BUTTON form of the
 * same row, for an action that runs a script instead of going somewhere
 * (Copy customer link, Draft a reminder with AI on the invoice screen).
 * Those two used to carry .paintops-btn-secondary, so they rendered as
 * loose pills floating in an unpadded tinted box while every other panel
 * in the app was a tidy column of rows (Mike 2026-09-15). Deliberately
 * NOT a bare "button" selector: .po-jrow-move-item is a button inside
 * this same panel on the Jobs rows screen with its own accent styling,
 * and an element-plus-class selector would outrank it. */
.pv-more-actions a,
.pv-more-actions .pv-more-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 10px 14px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--po-line, #eee);
  border-radius: 0;
  color: var(--po-text, #333);
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.4;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.pv-more-actions a:last-child,
.pv-more-actions .pv-more-item:last-child {
  border-bottom: none;
}

/* The hovered row states its text color as well as its background. Leaving
 * the color to be inherited is what made a hovered button unreadable under
 * a theme with its own button:hover color rule. */
.pv-more-actions a:hover,
.pv-more-actions a:focus-visible,
.pv-more-actions .pv-more-item:hover,
.pv-more-actions .pv-more-item:focus-visible {
  background: var(--po-hover, #f0f0f0);
  color: var(--po-text, #333);
  /* Themes underline a hovered link; a menu row is not body copy. */
  text-decoration: none;
}

.pv-more-actions .pv-more-item:disabled {
  opacity: .6;
  cursor: default;
}

/* A script's status line ("Drafting...", "Could not reach the server")
 * belonging to a panel action, sitting just under the panel. Gone entirely
 * until something writes into it. */
.pv-more-note {
  display: block;
  margin: 6px 0 0;
  font-size: 13px;
  color: var(--po-text-muted, #6b7684);
}

.pv-more-note:empty {
  display: none;
}

.pv-more-actions .dashicons {
  font-size: 18px;
  width: 18px;
  height: 18px;
  color: var(--po-accent, #4682B4);
}

/* Job View Quick Actions as a tile grid (the .pv-quick-actions-tiles
 * modifier, only on the single-job view, NOT the list/board cards which
 * reuse plain .pv-quick-actions). Turns the old small text links
 * (Directions, Add Expense, Notes & Photos, More Actions) into equal,
 * thumb-sized icon-over-label tiles so they're easy to hit on a phone,
 * while staying visually secondary to the big Check In/Out box below them.
 * Placed after the base .pv-quick-actions rules so the grid display and the
 * tile look win over the flex/link styling for elements carrying both
 * classes. */
.pv-quick-actions-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 10px;
  align-items: stretch;
  margin: 12px 0 16px;
}
.pv-quick-actions-tiles > a,
.pv-quick-actions-tiles > .pv-dir-wrap,
.pv-quick-actions-tiles > .pv-more-wrap,
.pv-quick-actions-tiles .pv-more-toggle {
  margin: 0;
}
/* The shared tile face: applied to the plain action links, the More Actions
 * toggle, and the Directions main button (which lives inside .pv-dir-wrap). */
.pv-quick-actions-tiles > a,
.pv-quick-actions-tiles .pv-more-toggle,
.pv-quick-actions-tiles .pv-dir-main {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 64px;
  height: 100%;
  padding: 10px 8px;
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius-xs, 4px);
  background: var(--po-card-bg, #fff);
  color: var(--po-text, #2b3440) !important;
  font-weight: 600;
  font-size: 13px;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  box-sizing: border-box;
}
.pv-quick-actions-tiles > a:hover,
.pv-quick-actions-tiles .pv-more-toggle:hover,
.pv-quick-actions-tiles .pv-dir-main:hover {
  background: var(--po-accent-soft, rgba(70,130,180,0.08));
  text-decoration: none;
}
.pv-quick-actions-tiles .dashicons {
  font-size: 24px !important;
  width: 24px !important;
  height: 24px !important;
}
/* Directions is a split control (main button + a small gear to pick the
 * maps app). Make the wrapper the tile cell, the main button fill it, and
 * tuck the gear into the top-right corner; its dropdown still opens below. */
.pv-quick-actions-tiles > .pv-dir-wrap {
  position: relative;
  display: block;
}
.pv-quick-actions-tiles .pv-dir-main {
  width: 100%;
}
.pv-quick-actions-tiles .pv-dir-gear {
  position: absolute;
  top: 5px;
  right: 5px;
  margin: 0;
  padding: 2px;
  z-index: 2;
}
.pv-quick-actions-tiles .pv-dir-menu {
  top: 100%;
  left: 0;
}

/* More Actions is the same shape of control as Directions: a tile with a
 * menu hanging off it. The wrapper is the grid cell, the toggle fills it.
 * Before this the panel was a sibling of the whole GRID, so it opened at
 * the grid's left edge no matter where its tile was (Mike 2026-09-15:
 * "menu is shifted to the left"). */
.pv-quick-actions-tiles {
  position: relative;
}
.pv-quick-actions-tiles > .pv-more-wrap {
  display: block;
}
.pv-quick-actions-tiles .pv-more-wrap > .pv-more-toggle {
  width: 100%;
}

/* The menu FLOATS at every width, it just measures from a different edge.
 *
 * NARROW: the containing block is the BAR (the wrapper is not positioned
 * here), so the menu opens full width under the whole bar, left aligned.
 * It has to be, because at two or three columns the More Actions tile can
 * be the FIRST cell in its row and a ~280px menu measured from that tile's
 * right edge would hang off the left of the screen. It also cannot simply
 * sit in normal flow any more: inside a grid cell it would be as narrow as
 * the tile, about 126px, which is unreadable.
 *
 * WIDE: the wrapper becomes the containing block and the menu hangs from
 * its own tile's right edge, which is the whole point of the fix. */
/* POSITIONED IN BOTH STATES, not only when open (Mike 2026-09-29: "the more
 * actions menu on the view job page jumps when clicked").
 *
 * This geometry used to hang off .show, so a CLOSED panel was still in normal
 * flow: .hidden-box only sets max-height:0 and overflow:hidden, which hides
 * the CONTENT but leaves the box's own border and its 20px margin-bottom
 * taking up room. Measured on the job view, the actions bar was 123px tall
 * closed and 101px open, so the whole page below it jumped up 22px on the
 * first click and back down on the second. Taking it out of flow in both
 * states means opening the menu changes nothing but the menu. */
.pv-quick-actions-tiles > .pv-more-wrap > .pv-more-actions {
  position: absolute;
  top: 100%;
  margin-top: 6px;
  margin-bottom: 0;
  left: 0;
  right: auto;
  width: 300px;
  max-width: 100%;
  z-index: 60;
}

@media (min-width: 640px) {
  .pv-quick-actions-tiles > .pv-more-wrap {
    position: relative;
  }
  .pv-quick-actions-tiles > .pv-more-wrap > .pv-more-actions {
    left: auto;
    right: 0;
    width: 280px;
    max-width: none;
  }
}
.pv-quick-actions-tiles .pv-more-toggle .pv-more-chevron {
  font-size: 10px;
}

/* Crisp SVG line-icons for the job action tiles / menus (Mike 2026-09-10),
 * same family as the sidebar so the Job View row and the main menu match.
 * currentColor inherits the link color; sizes mirror the dashicons they
 * replace in each context. */
.po-act-ic {
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.pv-quick-actions .po-act-ic,
.pv-more-actions .po-act-ic { width: 18px; height: 18px; }
.pv-more-actions .po-act-ic { color: var(--po-accent, #4682B4); }
.pv-quick-actions-tiles .po-act-ic { width: 24px; height: 24px; }

/* Active section highlight: the tile / menu item for the page you're on
 * (used by the shared job actions bar on sub-section pages). In the tile
 * grid it inverts to a filled accent chip; in the More Actions panel it
 * gets a soft accent wash + bold label. */
.pv-quick-actions-tiles > a.po-act-active,
.pv-quick-actions-tiles .pv-more-toggle.po-act-active {
  background: var(--po-accent, #4682B4);
  border-color: var(--po-accent, #4682B4);
  color: #fff !important;
}
.pv-quick-actions-tiles > a.po-act-active:hover,
.pv-quick-actions-tiles .pv-more-toggle.po-act-active:hover {
  background: var(--po-accent, #4682B4);
  filter: brightness(0.96);
}
.pv-more-actions a.po-act-active {
  background: var(--po-accent-soft, rgba(70,130,180,0.10));
  font-weight: 700;
  color: var(--po-accent, #4682B4);
  box-shadow: inset 3px 0 0 var(--po-accent, #4682B4);
}
.pv-more-actions a.po-act-active .po-act-ic { color: var(--po-accent, #4682B4); }

/* The shared bar sits at the very top of a sub-section page, give it a
 * touch more breathing room below than the in-page Quick Actions row. */
.po-job-actions-bar { margin-top: 4px; }

/* "Admin only" reassurance note above the Job Profit Report Summary cards
 * (Mike 2026-09-10), so an owner knows the crew can't see profit figures. */
.po-admin-only-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 0 0 14px;
  padding: 9px 12px;
  border-radius: 8px;
  background: var(--po-accent-soft, rgba(70,130,180,0.10));
  border: 1px solid var(--po-accent, #4682B4);
  color: var(--po-text, #2b3440);
  font-size: 13px;
  line-height: 1.45;
}
.po-admin-only-note strong { color: var(--po-heading, #20374f); }
.po-admin-only-ic {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 1px;
  stroke: var(--po-accent, #4682B4);
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Job Card Components */
.job-card {
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius, 6px);
  padding: var(--po-s4, 16px);
  margin-bottom: var(--po-s4, 16px);
  background: var(--po-card-bg, #fff);
  box-shadow: none;
}

/* NO HORIZONTAL PADDING (Mike 2026-09-21: "please left align the job title
 * and pills to the rest of the contents"). This header used to carry a
 * tinted background, and 10px 12px was the inset for it. The background
 * went transparent in the 2026-09-13 visual pass and the padding stayed,
 * so on every search result the title and the status chips sat 12px
 * further in than the scheduled line, the address and the Actions row
 * below them. The margin below already supplies the gap. */
.job-card-header {
  background: transparent;
  margin: 0 0 .75rem;
  padding: 0;
  border-radius: var(--po-radius-xs, 4px);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Inside a card this is a card title, not a page title, so it steps down. */
.job-card .job-title {
  font-size: var(--po-fs-card, 15px);
  font-weight: 600;
  color: var(--po-heading, #2f5aae);
  margin: 0;
}

/* Small status chips on each job card: type (Quote/Estimate/Bid/Job),
 * completion, and the "Starts in/Started X ago" note, replaces the
 * plain, unstyled "Complete? No" text that used to float above the card
 * with something that reads at a glance. */
.job-card-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* (2026-09-13 visual pass) Status chips were 12px-radius pills at .75rem.
 * A record can carry three or four of them at once, so at that size they
 * out-weighed the customer name they were describing. Now a small squared
 * tag: same colors, same information, a third of the visual noise. */
.job-badge {
  display: inline-block;
  padding: 2px 7px;
  border-radius: 3px;
  font-size: var(--po-fs-label, 11px);
  font-weight: 600;
  letter-spacing: .02em;
  line-height: 1.6;
  white-space: nowrap;
}

/* Each badge fixes its own background, so it fixes its own ink: a themed
 * token here puts a dark theme's light text on a light pill. Measured
 * 2026-09-14, every pair at or above 4.5:1. */
.job-badge-quote     { background: #eef1f4; color: #5c6672; }
.job-badge-estimate  { background: #e8f0fe; color: #2f5aae; }
.job-badge-bid       { background: #fdf3e0; color: #7d5100; }
.job-badge-job       { background: #e6f4ea; color: #1b6e2e; }
.job-badge-complete-yes { background: #e6f4ea; color: #1b6e2e; }
.job-badge-complete-no  { background: #fdecea; color: #b3261e; }
.job-badge-muted     { background: #f1f1f1; color: #6b6b6b; }

.job-meta {
  font-size: .9rem;
  color: var(--po-text-muted, #666);
  display: flex;
  justify-content: space-between;
  margin-bottom: .75rem;
}

.job-meta a {
  color: var(--po-accent, #4682B4);
  text-decoration: none;
}

.job-meta a:hover {
  text-decoration: underline;
}

.job-desc {
  margin: 0;
  font-size: 1rem;
  line-height: 1.4;
}

/* Job Title Section */
/* (2026-09-13 visual pass) This is the <h1> on the Job View screen, i.e. the
 * record's name, so it matches .po-screen-title exactly rather than being a
 * second, heavier title style. Same size, same weight, same ink: one page
 * title look in the whole app. */
.job-title {
  font-size: var(--po-fs-page, 21px);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: var(--po-heading, #293241);
  margin: 0 0 var(--po-s3, 12px);
  word-break: break-word;
}
@media (max-width: 782px) { .job-title { font-size: 19px; } }

.job-id,
.job-name {
  display: block;
  line-height: 1.3;
}

/* Responsive Layout */
@media (max-width: 768px) {
  .addr-row {
    grid-template-columns: 1fr; /* one per line */
  }

  table {
    width: 100%;
    table-layout: fixed;
    word-wrap: break-word;
  }

  td {
    display: block;
    width: 100%;
    box-sizing: border-box;
    padding: 6px 0;
  }

  tr {
    display: block;
    margin-bottom: 8px;
  }

  tr td:first-child {
    font-weight: bold;
    background: var(--po-tile-bg, #f5f5f5);
  }
}

/* PaintClarity Buttons
 *
 * Every page under the app (Customers, Financials, Paint Colors,
 * Calendar, Resources, etc.) uses the WP-admin-style class names
 * button / button-primary / button-secondary for its action buttons.
 * Those classes carry no real styling of their own on the front end,
 * so a real <button> element's OS-native chrome (or whatever generic
 * .button rule the active theme happens to ship) was what actually
 * rendered, both bigger and visually inconsistent with the compact
 * nav pills above. This gives every one of those buttons one shared,
 * compact look that matches the nav bar's sizing and accent color,
 * loaded once here since job-style.php is required on every app page.
 */
.button,
a.button,
button.button,
input.button {
  display: inline-block;
  box-sizing: border-box;
  margin: 0;
  padding: 8px 14px !important;
  font-size: var(--po-fs-body, 14px) !important;
  font-weight: 600;
  line-height: 1.45 !important;
  height: auto !important;
  border-radius: var(--po-radius-xs, 4px);
  /* (2026-09-13) Was an accent-colored border and accent text, which made
   * every incidental "Search" / "Rename" / "Filter" button look like a call
   * to action. These are secondary by definition, so they take the neutral
   * border and the text ink. */
  border: 1px solid var(--po-input-border, #ccd6de);
  background: var(--po-card-bg, #fff);
  color: var(--po-text, #2b3440);
  cursor: pointer;
  text-decoration: none;
}

.button:hover,
a.button:hover,
button.button:hover,
input.button:hover {
  background: var(--po-hover, #f4f7fa);
  border-color: var(--po-accent-line, rgba(61,90,128,.35));
}

/* (2026-09-15) THE HOVERED INK, STATED. The rule above sets a background
 * and a border and says nothing about the text, which leaves the hovered
 * text color to whatever the active WordPress theme declares for
 * button:hover / a:hover. Plenty of themes declare a dark-fill/white-text
 * hover: the background half of that loses to the rule above (one class
 * beats one element), the color half wins, and the result is white text
 * on this button's near-white hover fill. Measured at 1.09:1 on the
 * invoice reminder row, which is what Mike reported on 2026-09-15 as
 * "hovering over buttons makes the text unreadable". Pinning it brings
 * the same button back to 11.59:1.
 *
 * Scoped to the app canvas for SPECIFICITY, NOT !important, and the
 * difference matters: two classes plus an element beats any theme rule,
 * while an inline style still wins. Several buttons in this plugin carry
 * their own inline color and background (the red Delete rows, the
 * calculator's solid green "Add These Numbers to Quote"), and !important
 * here would repaint every one of them on hover. */
.paintops-app-canvas-inner .button:hover,
.paintops-app-canvas-inner .button:focus,
.paintops-app-canvas-inner .button:active,
.paintops-shell .button:hover,
.paintops-shell .button:focus,
.paintops-shell .button:active {
  background: var(--po-hover, #f4f7fa);
  border-color: var(--po-accent-line, rgba(61,90,128,.35));
  color: var(--po-text, #2b3440);
}

/* button-primary keeps its job: the one solid action in a form. */
.button-primary,
a.button-primary,
button.button-primary,
input.button-primary {
  background: var(--po-accent, #4682B4);
  border-color: var(--po-accent, #4682B4);
  color: #fff;
}
.button-primary:hover,
a.button-primary:hover,
button.button-primary:hover,
input.button-primary:hover {
  background: var(--po-accent, #4682B4);
  filter: brightness(1.07);
}

/* Same reasoning, same scoping, as the .button hover above: the ink that
 * sits on the accent fill is stated so a theme cannot swap it out.
 * --po-on-accent is the theme's ink for that fill, white on every light
 * theme and flipped by the dark one. */
.paintops-app-canvas-inner .button-primary:hover,
.paintops-app-canvas-inner .button-primary:focus,
.paintops-shell .button-primary:hover,
.paintops-shell .button-primary:focus {
  background: var(--po-accent, #4682B4);
  color: var(--po-on-accent, #fff);
  filter: brightness(1.07);
}

/* App-page links pick up the theme accent instead of whatever default
 * blue the WP theme ships, bare <a> tags (customer names on board
 * cards, calendar job links, Related Visits dates) were the single
 * most visible off-theme element on the live site. Low specificity on
 * purpose: anything with its own class-based color rule (buttons, nav
 * pills, badges, the admin bar's #wpadminbar rules) still wins. */
a {
  color: var(--po-accent, #4682B4);
}

/* Hide the WP theme's own page header/title on app pages. The theme
 * was printing a giant "Jobs" H1 above the app on every route, it
 * duplicated the app's own headings (e.g. right above "Job #1 ...")
 * and pushed real content nearly a full screen down on phones. Covers
 * the common classic-theme and block-theme title markup; the document
 * <title> (and the section-label filter feeding it) is unaffected.
 * (2026-07-19) The old companion rule that merely SIZE-CAPPED the
 * title on themes with wrapper markup this list did not match (Neve's
 * .nv-page-title etc.) was folded in here: with the unified app bar
 * the theme H1 read as a duplicate of the app's own screen headings
 * (Mike's report, e.g. "Business" twice), and every screen where it
 * showed either duplicates a better in-app heading or is deliberately
 * title-less (matching full-screen canvas mode, which never had the
 * theme H1). So the theme title is now hidden on app pages across ALL
 * known markup, never just resized. */
.entry-header,
h1.entry-title,
h1.page-title,
h1.wp-block-post-title,
.nv-page-title,
.nv-page-title-wrap,
.page-header-inner h1,
header.entry-header h1,
.wp-block-post-title {
  display: none !important;
}

/* Primary save buttons (the sticky Save bar and the plain save row).
 * Some themes style bare <button>/.wp-element-button elements with
 * their own brand color at higher specificity than .job-form button
 * above, which made "Create" render WordPress-blue in the middle of
 * an otherwise accent-colored app. !important so the app's primary
 * action is always the app's accent, on any theme. */
.paintops-sticky-save button,
.paintops-form-save button,
.job-form button[type="submit"] {
  background: var(--po-accent, #4682B4) !important;
  color: #fff !important;
  border: none !important;
  border-radius: var(--po-radius-xs, 4px) !important;
  padding: 0.7rem 2.2rem !important;
  font-size: 15px !important;
  font-weight: 600;
  cursor: pointer;
}


/* Rows-based Jobs home (2026-08-31 redesign, includes/job-rows.php).
 * Grouped row lists: uppercase section labels, one card per section,
 * a fixed grid per row (status / job / crew or meta / date or action /
 * money) so the page scans identically every day. All colors ride the
 * --po-* theme variables. */
/* (2026-09-13 visual pass) Group labels were 13px/800 in the heading ink,
 * which is a heading's weight on a divider's job. Same eyebrow treatment as
 * every other section label in the app now, so the row cards below them are
 * what the eye lands on. */
.po-jrows-head { display: flex; align-items: baseline; gap: var(--po-s2, 8px); margin: var(--po-s5, 24px) 0 var(--po-s2, 8px); }
.po-jrows-head:first-child { margin-top: 0; }
.po-jrows-label { font-size: var(--po-fs-label, 11px); font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--po-text-muted, #5b6673); }
.po-jrows-meta { font-size: var(--po-fs-meta, 13px); font-weight: 500; color: var(--po-text-soft, #97a2ae); }
/* overflow is VISIBLE on purpose (Mike 2026-09-13): the row Actions menu
 * now floats below its row instead of expanding inline, and the last row's
 * menu hangs past the card's bottom edge. Nothing inside a row paints a
 * background, so the rounded corners still read clean without clipping.
 * container-type stays: it only adds layout/style containment, never paint. */
.po-jrows-card { background: var(--po-card-bg, #fff); border: 1px solid var(--po-card-border, #ddd); border-radius: var(--po-radius, 6px); box-shadow: none; overflow: visible; container-type: inline-size; }
/* (2026-09-15) Columns are status / WHEN / what / who or next step / money /
 * actions. The date used to be the FOURTH column, four cells away from the
 * status it belongs with, so the status cell had to carry its own copy of it
 * ("Started Sep 9") while "Scheduled" carried none. The date sits beside the
 * status now and the status is just the state. */
/* The last track was `auto` until 2026-09-19. Every row is its own grid, not
 * a subgrid, so an `auto` track measures that row's own content: the header
 * strip's empty sixth cell resolved to 0px while a row's Actions toggle
 * resolved to ~63px, and the 1fr title column swallowed the difference.
 * The result was a header whose CREW and VALUE labels sat 63px to the right
 * of the values they named. A fixed track makes every row agree; 76px
 * holds "Actions" plus its caret with room for a theme's larger font. */
.po-jrow { position: relative; display: grid; grid-template-columns: 130px 120px minmax(0, 1fr) 130px 110px 76px; gap: var(--po-s3, 12px); align-items: center; padding: var(--po-s3, 12px) var(--po-s4, 16px); border-top: 1px solid var(--po-line, #f1f4f7); }
.po-jrow:hover { background: var(--po-hover, #f6f9fb); }

/* COLUMN HEADER STRIP (Mike 2026-09-19). Same grid as .po-jrow so the
 * labels sit over their own cells. Hidden below 900px, where .po-jrow
 * collapses to two columns and each cell shows its own .po-jrow-clabel
 * instead, so a header strip there would label the wrong things. */
.po-jrow-head {
  padding-top: 8px; padding-bottom: 8px;
  background: var(--po-tile-bg, #fafbfc);
  border-top: none;
  border-bottom: 1px solid var(--po-card-border, #ddd);
  font-size: var(--po-fs-label, 11px); font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--po-text-muted, #5b6673);
}
.po-jrow-head:hover { background: var(--po-tile-bg, #fafbfc); }
.po-jrow-head .po-jrow-money { text-align: right; display: block; font-size: inherit; font-weight: inherit; color: inherit; }
@media (max-width: 900px) { .po-jrow-head { display: none; } }
@container (max-width: 760px) { .po-jrow-head { display: none; } }

/* SECTION ACCORDIONS (Mike 2026-09-19): Quotes Out and Needs Your
 * Attention fold under Active and Upcoming. The summary reuses the
 * section-heading type so an open accordion and a plain section read as
 * the same thing. */
.po-jrows-acc { margin: var(--po-s5, 24px) 0 0; }
.po-jrows-accsum {
  display: flex; align-items: baseline; gap: var(--po-s2, 8px);
  cursor: pointer; padding: 4px 2px; margin: 0 0 var(--po-s2, 8px);
  list-style: none;
}
.po-jrows-accsum::-webkit-details-marker { display: none; }
.po-jrows-accsum::after {
  content: ''; width: 7px; height: 7px; margin-left: 2px;
  border-right: 2px solid var(--po-text-soft, #97a2ae);
  border-bottom: 2px solid var(--po-text-soft, #97a2ae);
  transform: rotate(45deg) translateY(-2px);
  transition: transform .15s ease;
}
.po-jrows-acc[open] > .po-jrows-accsum::after { transform: rotate(-135deg) translateY(0); }
/* A COLLAPSED SECTION GETS AN OUTLINE (Mike 2026-09-20). Folded away, the
 * three summaries were three lines of text floating on the page background
 * with nothing to say they were things you could open. A 1px border and the
 * card's radius give them the same footprint the open card has, so the fold
 * reads as the card closing rather than the card vanishing.
 *
 * OUTLINE ONLY, NO FILL, on purpose: a filled strip competes with the real
 * cards above it, and the open section already carries the surface. The
 * border is dropped while the section is open, where the card below it is
 * doing that job and a second box around it would be a box in a box. */
.po-jrows-acc > .po-jrows-accsum,
.paintops-alerts-acc > .paintops-alerts-summary {
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius-sm, 6px);
  padding: 10px 14px;
  transition: border-color .15s ease;
}
.po-jrows-acc[open] > .po-jrows-accsum,
.paintops-alerts-acc[open] > .paintops-alerts-summary {
  border-color: transparent;
  padding: 4px 2px;
}
.po-jrows-acc:not([open]) > .po-jrows-accsum:hover,
.paintops-alerts-acc:not([open]) > .paintops-alerts-summary:hover {
  border-color: var(--po-accent, #437bab);
}
.po-jrows-accsum:hover .po-jrows-label { color: var(--po-accent, #437bab); }
.po-jrows-accsum:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: 2px; border-radius: 4px; }

.paintops-alerts-acc { margin: var(--po-s5, 24px) 0 0; }
.paintops-alerts-summary {
  display: flex; align-items: center; gap: var(--po-s2, 8px);
  cursor: pointer; padding: 4px 2px; margin: 0 0 var(--po-s2, 8px);
  list-style: none; flex-wrap: wrap;
}
.paintops-alerts-summary::-webkit-details-marker { display: none; }
.paintops-alerts-sum-title {
  font-size: var(--po-fs-label, 11px); font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--po-text-muted, #5b6673);
}
.paintops-alerts-sum-count {
  font-size: var(--po-fs-meta, 12px); font-weight: 700;
  padding: 1px 8px; border-radius: var(--po-radius-pill, 20px);
  background: #fff3d6; color: #7a6015;
}
.paintops-alerts-sum-meta { font-size: var(--po-fs-meta, 13px); color: var(--po-text-soft, #97a2ae); }
.paintops-alerts-summary::after {
  content: ''; width: 7px; height: 7px; margin-left: 2px;
  border-right: 2px solid var(--po-text-soft, #97a2ae);
  border-bottom: 2px solid var(--po-text-soft, #97a2ae);
  transform: rotate(45deg) translateY(-2px);
  transition: transform .15s ease;
}
.paintops-alerts-acc[open] > .paintops-alerts-summary::after { transform: rotate(-135deg) translateY(0); }
.paintops-alerts-summary:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: 2px; border-radius: 4px; }

/* The Jobs action row, directly under the page masthead: New Job on the
 * left, the View All / Board switch links pushed right (Mike 2026-09-20).
 *
 * This replaces .po-jobs-titlerow, which held the TITLE as a flex item
 * alongside these. That is what looked misaligned: the masthead carries
 * the header rule as its own border-bottom, so as a flex item the rule
 * spanned only the title's share of the row and stopped before the button,
 * while .po-jrows-foot brought a second rule at a different height. One
 * masthead rule, then a clean row. Same shape as .po-biz-head on the
 * Expenses page, deliberately: button left, the rest pushed right by
 * margin-left:auto, wrapping on a phone.
 *
 * The masthead's own 24px bottom margin is pulled back here so the actions
 * sit close under the rule they belong to rather than floating halfway to
 * the first table; the 24px is handed to the row instead. */
/* space-between, NOT margin-left:auto on the links, and that choice is
 * doing real work. On one line it reads button-left / links-right, which
 * is the Expenses shape. When the row wraps on a phone each line holds a
 * single item, and a lone item under space-between sits at the START, so
 * the button and the links below it share a left edge instead of the links
 * being flung to the right margin on their own row. margin-left:auto gets
 * the one-line case right and the wrapped case wrong, and there is no
 * breakpoint that can tell you which one you are in. */
.po-jobs-actions { display: flex; align-items: center; justify-content: space-between; gap: var(--po-s3, 12px) var(--po-s4, 16px); flex-wrap: wrap; margin: calc(var(--po-s5, 24px) * -1 + var(--po-s3, 12px)) 0 var(--po-s5, 24px); }
/* The links keep their accent styling and lose the rule and the spacing
 * they carry when they stand alone; in this row the masthead above is what
 * closes the header off. */
.po-jobs-actions .po-jrows-foot { margin: 0; padding: 0; border-bottom: 0; flex: 0 1 auto; }
.po-jobs-newbtn { flex: 0 0 auto; }
.po-jobs-newbtn .dashicons { margin-right: 4px; vertical-align: text-bottom; }

/* COMMUNICATION SECTION (Mike 2026-09-19). The picker sits INSIDE the card,
 * above the rows, because choosing who to contact and seeing who was
 * contacted last are one thought. On a phone the three controls stack and
 * go full width, which is the only way a select and two buttons stay
 * tappable at that measure. */
.po-comms-start {
  display: flex; align-items: center; gap: var(--po-s2, 8px); flex-wrap: wrap;
  padding: var(--po-s3, 12px) var(--po-s4, 16px);
  border-bottom: 1px solid var(--po-card-border, #ddd);
  background: var(--po-tile-bg, #fafbfc);
}
.po-comms-start label {
  font-size: var(--po-fs-label, 11px); font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--po-text-muted, #5b6673);
  margin: 0;
}
.po-comms-start select {
  flex: 1 1 260px; min-width: 0; max-width: 460px;
  margin: 0;
}
.po-comms-start .paintops-btn { flex: 0 0 auto; margin: 0; }
/* The decline button ships with the `hidden` attribute and JavaScript takes
 * it off when a declinable record is picked. `hidden` is only display:none
 * in the user-agent sheet, so .paintops-btn's own display beats it and the
 * button stayed on screen. Restated here, where it outranks the button
 * rules, and matched on the attribute so nothing has to remember a second
 * class. (Same trap as .pbn-sheet[hidden] in app-nav.css.) */
.po-comms-start .paintops-btn[hidden] { display: none; }
/* The decline is a destructive-ish write sitting next to a harmless one, so
 * it reads quieter than the primary and only warms up on hover. */
.po-comms-decline { font-weight: 600; }
.po-comms-decline:hover { border-color: #c9a227; color: #7a6015; }
@media (max-width: 700px) {
  .po-comms-start { align-items: stretch; }
  .po-comms-start select,
  .po-comms-start .paintops-btn { flex: 1 1 100%; max-width: none; }
}

/* FILE A RECEIPT, THREE ACCORDIONS (Mike 2026-09-20). Same control as the
 * Business page sections and the Jobs page: outlined while shut so a closed
 * form reads as a card that is shut, border dropped while open because the
 * body below is drawing that box. */
.po-receipt-acc { margin: 0 0 12px; }
.po-receipt-sum {
  display: flex; align-items: flex-start; gap: 12px;
  cursor: pointer; list-style: none;
  padding: 14px 16px;
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius-sm, 6px);
  transition: border-color .15s ease;
}
.po-receipt-sum::-webkit-details-marker { display: none; }
.po-receipt-acc[open] > .po-receipt-sum { border-color: transparent; padding-bottom: 4px; }
.po-receipt-acc:not([open]) > .po-receipt-sum:hover { border-color: var(--po-accent, #437bab); }
.po-receipt-sum:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: 2px; }
.po-receipt-icon {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 10px;
  background: #eaf1f8; color: var(--po-accent, #20374f);
  display: grid; place-items: center;
}
.po-receipt-head { display: block; min-width: 0; flex: 1 1 auto; }
.po-receipt-head strong { display: block; font-size: 15px; }
.po-receipt-note { display: block; margin-top: 3px; color: var(--po-text-muted, #6b7480); font-size: 12.5px; }
/* The caret sits on the right and keeps its own column, so a long note never
 * pushes it off the row. */
.po-receipt-sum::after {
  content: ''; flex: 0 0 auto; align-self: center;
  width: 8px; height: 8px; margin-left: 4px;
  border-right: 2px solid var(--po-text-soft, #97a2ae);
  border-bottom: 2px solid var(--po-text-soft, #97a2ae);
  transform: rotate(45deg) translateY(-2px);
  transition: transform .15s ease;
}
.po-receipt-acc[open] > .po-receipt-sum::after { transform: rotate(-135deg) translateY(0); }
.po-receipt-body { padding: 4px 16px 16px; }
@media (max-width: 620px) {
  .po-receipt-sum { padding: 12px; gap: 10px; }
  .po-receipt-body { padding: 4px 12px 14px; }
}

/* REACH THE CUSTOMER (Mike 2026-09-20): the outbound strip at the top of
 * Message History. Segmented, like the Not Won drawer's contact links, so
 * the actions read as one control rather than a line of loose links. Wraps
 * to as many rows as it needs; on a phone each action keeps a 44px tap
 * target, because this is the one panel on the page you use while standing
 * in someone's driveway. */
.po-reach-lab {
  font-size: var(--po-fs-label, 11px); font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--po-text-muted, #5b6673);
  margin: 0 0 var(--po-s2, 8px);
}
.po-reach-row { display: flex; flex-wrap: wrap; gap: var(--po-s2, 8px); }
.po-reach-act {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 38px; padding: 6px 14px;
  font-size: var(--po-fs-body, 14px); font-weight: 600;
  color: var(--po-accent, #437bab);
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius-sm, 6px);
  background: var(--po-card-bg, #fff);
}
.po-reach-act:hover { border-color: var(--po-accent, #437bab); background: var(--po-hover, #f6f9fb); }
.po-reach-act:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: 2px; }
@media (max-width: 700px) {
  .po-reach-act { min-height: 44px; flex: 1 1 calc(50% - var(--po-s2, 8px)); }
}

/* RECORD A CONTACT, folded (Mike 2026-09-20). The summary reads as the
 * heading it replaced, so an open accordion and the old always-open card
 * look like the same thing. */
.po-comm-acc { margin: 0 0 18px; }
.po-comm-acc > .cs-financial-box { margin: 0 !important; }
.po-comm-accsum {
  display: inline-flex; align-items: center; gap: var(--po-s2, 8px);
  cursor: pointer; list-style: none;
  padding: 4px 2px; margin: 0 0 var(--po-s2, 8px);
  font-size: var(--po-fs-card, 15px); font-weight: 700;
  color: var(--po-text, #2b3440);
}
.po-comm-accsum::-webkit-details-marker { display: none; }
.po-comm-accsum::after {
  content: ''; width: 7px; height: 7px;
  border-right: 2px solid var(--po-text-soft, #97a2ae);
  border-bottom: 2px solid var(--po-text-soft, #97a2ae);
  transform: rotate(45deg) translateY(-2px);
  transition: transform .15s ease;
}
.po-comm-acc[open] > .po-comm-accsum::after { transform: rotate(-135deg) translateY(0); }
.po-comm-accsum:hover { color: var(--po-accent, #437bab); }
.po-comm-accsum:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: 2px; border-radius: 4px; }

/* REQUEST PERFORMANCE (Mike 2026-09-19). The teaser is the whole card
 * reduced to its one number, linking to the page that carries the rest.
 * It lives on the New Quote screen, where the rate is a useful thing to
 * glance at before you price something. */
.po-reqperf-teaser {
  display: flex; align-items: center; gap: var(--po-s3, 12px); flex-wrap: wrap;
  text-decoration: none; color: inherit;
  padding: var(--po-s3, 12px) var(--po-s4, 16px);
  margin: 0 0 var(--po-s4, 16px);
  border: 1px solid var(--po-card-border, #ddd);
  border-radius: var(--po-radius-sm, 6px);
  background: var(--po-card-bg, #fff);
}
.po-reqperf-teaser:hover { border-color: var(--po-accent, #437bab); }
.po-reqperf-teaser:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: 2px; }
.po-reqperf-teaser-fig { display: flex; align-items: baseline; gap: var(--po-s2, 8px); flex: 0 0 auto; }
.po-reqperf-teaser-lab {
  font-size: var(--po-fs-label, 11px); font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--po-text-muted, #5b6673);
}
.po-reqperf-teaser-val { font-size: 20px; font-weight: 700; line-height: 1; }
.po-reqperf-teaser-meta { font-size: var(--po-fs-meta, 13px); color: var(--po-text-soft, #97a2ae); flex: 1 1 auto; }
.po-reqperf-teaser-go { font-size: var(--po-fs-meta, 13px); font-weight: 600; color: var(--po-accent, #437bab); flex: 0 0 auto; }

/* The year chart. One row per year: label, track, rate. The fill is the
 * rate already stored on the row, so the bar and the number beside it can
 * never disagree. */
.po-reqperf-chart { display: flex; flex-direction: column; gap: var(--po-s2, 8px); }
.po-reqperf-bar { display: grid; grid-template-columns: 52px minmax(0, 1fr) 56px; gap: var(--po-s3, 12px); align-items: center; }
.po-reqperf-bar-yr { font-size: var(--po-fs-meta, 13px); font-weight: 700; color: var(--po-text-muted, #5b6673); }
.po-reqperf-bar-track {
  display: block; height: 10px; border-radius: var(--po-radius-pill, 20px);
  background: var(--po-tile-bg, #f0f3f6); overflow: hidden;
}
.po-reqperf-bar-fill {
  display: block; height: 100%; min-width: 2px;
  border-radius: var(--po-radius-pill, 20px);
  background: var(--po-accent, #437bab);
}
.po-reqperf-bar-rate { font-size: var(--po-fs-meta, 13px); font-weight: 700; text-align: right; color: var(--po-text, #2b3440); }

.po-reqperf-acc { margin: var(--po-s4, 16px) 0 0; }
.po-reqperf-accsum {
  display: inline-flex; align-items: center; gap: var(--po-s2, 8px);
  cursor: pointer; list-style: none;
  font-size: var(--po-fs-meta, 13px); font-weight: 600;
  color: var(--po-accent, #437bab);
}
.po-reqperf-accsum::-webkit-details-marker { display: none; }
.po-reqperf-accsum::after {
  content: ''; width: 7px; height: 7px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
  transition: transform .15s ease;
}
.po-reqperf-acc[open] > .po-reqperf-accsum::after { transform: rotate(-135deg) translateY(0); }
.po-reqperf-accsum:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: 2px; border-radius: 4px; }

.po-jrow:first-child { border-top: none; }
.po-jrow-empty { display: block; color: var(--po-text-muted, #667581); font-size: 14px; }
.po-jrow-status { font-size: var(--po-fs-meta, 13px); font-weight: 500; color: var(--po-text-muted, #667581); display: flex; align-items: center; gap: 7px; white-space: nowrap; }
.po-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--po-text-soft, #9aa5b1); }
.po-dot-green { background: var(--po-positive, #1a7f37); }
.po-dot-blue { background: var(--po-accent, #3D5A80); }
.po-dot-amber { background: #d97706; }
.po-dot-gray { background: var(--po-text-soft, #9aa5b1); }
.po-jrow-main { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
/* Accent (Mike 2026-09-10): row titles were --po-heading (dark navy) while
 * the Board's card titles are --po-accent, so the two views disagreed and the
 * row titles read as black. Match the Board so every job link is one color. */
.po-jrow-title { font-weight: 600; font-size: var(--po-fs-body, 14px); color: var(--po-accent, #4682B4); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.po-jrow-title:hover { text-decoration: underline; }
.po-jrow-sub { font-size: 13px; color: var(--po-text-muted, #667581); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.po-jrow-cell { font-size: var(--po-fs-meta, 13px); color: var(--po-text, #2b3440); }
.po-jrow-cell a { font-weight: 600; }
/* Money is the one place extra weight is earned: it is what the row is
 * ultimately about, and tabular figures let a column of them be scanned. */
.po-jrow-money { font-size: var(--po-fs-body, 14px); font-weight: 600; color: var(--po-heading, #2f5aae); text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.po-jrow-due { color: var(--po-negative, #c0392b); }
.po-jrow-dim { color: var(--po-text-soft, #9aa5b1); }
/* Tiny column labels on the right-side cells (Mike 2026-09-10): the crew,
 * date, and value columns had no headers, so the data read ambiguously.
 * A ~9px muted label above each value clarifies it, on desktop and when the
 * row reflows on small screens. */
.po-jrow-labeled { display: flex; flex-direction: column; justify-content: center; gap: 2px; }
.po-jrow-money.po-jrow-labeled { align-items: flex-end; }
/* Hidden on the wide layout since 2026-09-19: the .po-jrow-head strip above
 * the rows names these columns now, and printing CREW over every avatar and
 * VALUE over every figure as well was the same word twice in one glance.
 * The collapsed layouts below hide the strip and bring these back, because
 * there the cells scatter and each one has to say what it is. */
.po-jrow-clabel { display: none; font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--po-text-soft, #9aa5b1); line-height: 1; }
/* The date's own hide rule used to live here, from when it was the only
 * label hidden on the wide layout. Every .po-jrow-clabel is hidden there
 * now (see the rule above), so a selector naming just this one would only
 * out-specify the collapsed layouts' display:block and keep the date label
 * off on a phone, where it is needed. Removed 2026-09-19. */
.po-jrow-crew { display: block; }
/* The crew button is a plain control, not a theme button: every theme
   default (background, border, shadow, padding, min-height, transform) is
   reset here with !important because front-end themes style bare <button>
   elements with high-specificity rules of their own. */
.po-jrow-crew .po-jrow-crewbtn,
.po-jrow-crew .po-jrow-crewbtn:hover,
.po-jrow-crew .po-jrow-crewbtn:focus,
.po-jrow-crew .po-jrow-crewbtn:active { display: inline-flex !important; align-items: center !important; gap: 0 !important; margin: 0 !important; padding: 0 !important; min-height: 0 !important; min-width: 0 !important; height: auto !important; border: 0 !important; border-radius: 0 !important; background: none !important; box-shadow: none !important; color: inherit !important; font: inherit !important; line-height: 1 !important; text-align: left !important; text-transform: none !important; text-decoration: none !important; letter-spacing: normal !important; cursor: pointer !important; transform: none !important; appearance: none !important; -webkit-appearance: none !important; }
.po-jrow-crew .po-jrow-crewbtn:focus-visible { outline: 2px solid var(--po-accent, #2f6fed) !important; outline-offset: 2px !important; border-radius: 4px !important; }
.po-jrow-crewnames { list-style: none; margin: 4px 0 0; padding: 0; font-size: 12px; color: var(--po-text, #2b3440); }
.po-jrow-crewnames li { display: block; margin: 0; padding: 1px 0; }
.po-jrow-crewnames[hidden] { display: none; }
.po-jrow-avatar { width: 26px; height: 26px; border-radius: 50%; background: var(--po-accent, #3D5A80); color: #fff; font-size: 11px; font-weight: 600; display: inline-flex; align-items: center; justify-content: center; border: 2px solid var(--po-card-bg, #fff); }
.po-jrow-avatar + .po-jrow-avatar { margin-left: -8px; }
.po-jrow-avatar-more { background: var(--po-tile-bg, #eef2f6); color: var(--po-text-muted, #667581); }

/* Per-row Actions menu (Mike 2026-09-10): a compact "Actions" toggle in the
 * row's trailing cell, and a full-width panel that expands under the row with
 * the stage moves (Mark Completed, Back to Request, ...) and the standard job
 * actions. Text-link toggle, not a pill, to match the row look. */
.po-jrow-actions { justify-self: end; }
.po-jrow-actions-toggle { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 600; color: var(--po-accent, #4682B4) !important; white-space: nowrap; text-decoration: none; }
.po-jrow-actions-toggle:hover { text-decoration: underline; }
.po-jrow-actions-toggle .pv-more-chevron { font-size: 10px; }
/* Collapsed by default via display:none (not just max-height) so nothing
 * reserves space when closed. */
.po-jrow-actions-panel { display: none; }
/* Open state (Mike 2026-09-13): the panel used to be a grid item spanning
 * 1 / -1, which pinned it to the card's LEFT edge while its toggle sits on
 * the RIGHT, and shoved every row below it down the page. It is now a
 * floating menu: absolutely positioned against its own .po-jrow, right edge
 * lined up with the toggle (right:18px = the row's own padding), drawn over
 * the rows below instead of displacing them. Card surface + a real shadow so
 * it reads as a menu sitting above the page, not a panel cut into it. */
.po-jrow-actions-panel.show {
	display: block;
	position: absolute;
	top: calc(100% - 8px);
	right: 18px;
	z-index: 60;
	width: 260px;
	max-width: calc(100% - 36px);
	margin: 0;
	border: 1px solid var(--po-card-border, #ddd);
	border-radius: 10px;
	background: var(--po-card-bg, #fff);
	box-shadow: 0 12px 30px rgba(16, 24, 40, .18);
	overflow: hidden;
	/* A job with every action available runs ~12 items, which is taller than
	 * a phone screen, so cap it and let the menu scroll itself. */
	max-height: min(70vh, 560px);
	overflow-y: auto;
}
.po-jrow-move-form { margin: 0; display: block; }
.po-jrow-move-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 10px 14px; background: none; border: none; border-bottom: 1px solid var(--po-line, #eee); color: var(--po-accent, #4682B4); font-weight: 700; font-size: 14px; cursor: pointer; }
.po-jrow-move-item:hover { background: var(--po-hover, #f0f0f0); }
/* Per-user "show excluded jobs" toggle on Profits / Business (Mike
 * 2026-09-10). A quiet inline control, with a reassurance that the
 * accountant sheet is untouched. */
.po-excl-toggle { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 12px; margin: 0 0 18px; padding: 10px 14px; border: 1px solid var(--po-line, #e3e6ea); border-radius: 10px; background: var(--po-tile-bg, #f7f9fc); font-size: 13.5px; }
.po-excl-label { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; color: var(--po-text, #2b3440); cursor: pointer; }
.po-excl-label input { margin: 0; }
.po-excl-note { color: var(--po-text-muted, #6b7480); font-size: 12.5px; }
.po-excl-badge { display: inline-block; margin-left: 8px; font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--po-text-muted, #6b7480); border: 1px solid var(--po-line, #e3e6ea); border-radius: 999px; padding: 1px 8px; vertical-align: middle; }
.po-excl-table { width: 100%; border-collapse: collapse; margin: 10px 0 0; font-size: 14px; }
.po-excl-table th { text-align: left; font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--po-text-soft, #9aa5b1); padding: 4px 8px; border-bottom: 1px solid var(--po-line, #e3e6ea); }
.po-excl-table td { padding: 8px; border-bottom: 1px solid var(--po-line, #f1f4f7); }
.po-excl-table tr:last-child td { border-bottom: none; }

/* Jobs page header: title left, live search box pinned top-right of the
 * content (Mike 2026-09-10). Wraps to its own line on narrow screens. */
.po-jobs-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.po-jobs-head .po-screen-title { margin: 0; }

/* Live AJAX job search box + suggestions dropdown. */
.po-jobs-search { position: relative; flex: 0 1 340px; min-width: 200px; }
.po-jobs-search-ic {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; pointer-events: none;
  stroke: var(--po-text-muted, #6b7480); fill: none; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
/* The left padding is what keeps the magnifier off the placeholder, and it
 * is the one declaration a host theme reliably clobbers: a single class is
 * specificity (0,1,0), so any theme rule shaped like
 * `.site input[type="search"]` (0,1,1) wins and the icon lands on top of
 * "Search jobs...". Reported twice now, both times on the free build, which
 * is not a build difference at all: the free and premium stylesheets are
 * byte-identical here (verified 2026-09-14), it is whichever theme the site
 * runs. So this is scoped to the wrapper for specificity AND marked
 * important, the same treatment .paintops-btn already uses for the same
 * reason. box-sizing and height are pinned alongside it because a theme
 * that resets one usually resets all three. */
.po-jobs-search-input,
.po-jobs-search .po-jobs-search-input {
  width: 100%;
  box-sizing: border-box !important;
  height: 40px;
  padding: 0 12px 0 34px !important;
  border: 1px solid var(--po-input-border, #ccc); border-radius: 8px;
  font-size: 14px; background: var(--po-card-bg, #fff); color: var(--po-text, #333);
}
.po-jobs-search-input:focus {
  outline: none; border-color: var(--po-accent, #4682B4);
  box-shadow: 0 0 0 3px var(--po-accent-soft, rgba(70,130,180,0.15));
}
.po-jobs-search-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 60;
  background: var(--po-menu-bg, #fff);
  border: 1px solid var(--po-menu-border, #ddd); border-radius: 10px;
  box-shadow: 0 10px 30px rgba(16,24,40,.16);
  max-height: 60vh; overflow-y: auto;
}
.po-js-item {
  display: block; padding: 9px 12px; text-decoration: none; cursor: pointer;
  color: var(--po-menu-text, #2b3440); border-bottom: 1px solid var(--po-line, #eee);
}
.po-js-item:last-child { border-bottom: none; }
.po-js-item:hover, .po-js-item.is-active { background: var(--po-menu-hover, #eef7fb); }
.po-js-t { display: block; font-weight: 600; font-size: 14px; line-height: 1.3; }
.po-js-s { display: block; font-size: 12.5px; color: var(--po-text-muted, #6b7480); margin-top: 1px; }
.po-js-empty { padding: 12px; color: var(--po-text-muted, #6b7480); font-size: 13px; }

/* (2026-09-15) This row moved from the very bottom of the Jobs page to just
 * under Today's Crew, and it is sized to be FOUND while scanning rather than
 * to sit quietly out of the way: a touch larger, with room above and below
 * and a hairline closing it off from the first section label underneath. No
 * buttons, Mike's call; these stay links, they just stopped whispering. */
.po-jrows-foot { margin: 0 0 var(--po-s5, 24px); padding: 0 0 var(--po-s3, 12px); border-bottom: 1px solid var(--po-line, #e6eaef); font-size: 14.5px; display: flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; }
/* One consistent link style for this footer (Mike 2026-09-10): the "Completed
 * & all jobs" link and the Board/Row toggle button used to look different
 * (bold accent vs underlined gray). Both now read as the same accent link. */
.po-jrows-foot a, .po-jrows-foot button { font-weight: 700; color: var(--po-accent, #4682B4); text-decoration: none; }
/* Pin the accent color on hover too (Mike 2026-09-10): a theme's generic
 * button:hover was turning the toggle white on hover, making it vanish. The
 * :hover specificity here beats a bare button:hover so the color holds. */
.po-jrows-foot a:hover, .po-jrows-foot button:hover { background: none; color: var(--po-accent, #4682B4); text-decoration: underline; }

/* (2026-09-15) HOVER STATES THAT STATE BOTH HALVES. Mike, on Seaside:
 * "the job pipeline links such as Mark Completed are barely visible on
 * hover" and "hovering over Actions is barely readable". Measured with a
 * stock theme rule of the form `button:hover { background:#222; color:#fff }`:
 * a stage-move row came out at 1.09:1 (the theme's white ink on the app's
 * near-white hover fill, because the row states a fill and not an ink) and
 * the Actions toggle at 2.25:1 (the app's accent ink on the theme's near
 * black fill, because the toggle states an ink and not a fill). Whichever
 * half is left unsaid is the half the theme fills in, so every hover here
 * now says both. Scoped to the app wrappers for SPECIFICITY rather than
 * !important, for the same reason as the .button family: an inline style
 * on a single control must still win. */
.paintops-app-canvas-inner .po-jrow-actions-toggle:hover,
.paintops-app-canvas-inner .po-jrow-actions-toggle:focus,
.paintops-app-canvas-inner .pv-more-toggle:hover,
.paintops-app-canvas-inner .pv-more-toggle:focus,
.paintops-shell .po-jrow-actions-toggle:hover,
.paintops-shell .po-jrow-actions-toggle:focus,
.paintops-shell .pv-more-toggle:hover,
.paintops-shell .pv-more-toggle:focus {
  background: none;
  text-decoration: underline;
}

.paintops-app-canvas-inner .po-jrow-move-item:hover,
.paintops-app-canvas-inner .po-jrow-move-item:focus,
.paintops-shell .po-jrow-move-item:hover,
.paintops-shell .po-jrow-move-item:focus {
  background: var(--po-hover, #f0f0f0);
  color: var(--po-accent, #4682B4);
}

.paintops-app-canvas-inner .po-jrow-title:hover,
.paintops-app-canvas-inner .po-jrow-cell a:hover,
.paintops-shell .po-jrow-title:hover,
.paintops-shell .po-jrow-cell a:hover {
  background: none;
  color: var(--po-accent, #4682B4);
}
.po-jrows-sep { color: var(--po-text-soft, #c3cad3); }
/* Below this the two links stack, and a separator that ends a line instead
 * of sitting between two things is just a stray mark. */
@media (max-width: 520px) { .po-jrows-sep { display: none; } }
@media (max-width: 900px) {
  .po-jrow { grid-template-columns: minmax(0, 1fr) auto; }
  .po-jrow-clabel { display: block; }
  .po-jrow-status { order: 1; }
  .po-jrow-money { order: 2; }
  .po-jrow-main { order: 3; grid-column: 1 / -1; }
  .po-jrow-cell { order: 4; }
  .po-jrow-actions { order: 5; justify-self: start; }
  /* no `order` for the panel any more: it floats (position:absolute), so it
   * is out of grid flow and orders nothing. It does have to follow its
   * toggle, which moves to the LEFT of the row in this layout, so flip the
   * menu's anchor to match. */
  .po-jrow-actions-panel.show { left: 18px; right: auto; }
}
/* Themes with a normal content column give the card ~640px even on desktop,
 * which starved the 1fr title cell to nothing. Collapse by the CARD's own
 * width so the rows read correctly inside any theme (the viewport rule above
 * stays as the fallback for browsers without container queries). */
@container (max-width: 760px) {
  .po-jrow { grid-template-columns: minmax(0, 1fr) auto; }
  .po-jrow-clabel { display: block; }
  .po-jrow-status { order: 1; }
  .po-jrow-money { order: 2; }
  .po-jrow-main { order: 3; grid-column: 1 / -1; }
  .po-jrow-cell { order: 4; }
  .po-jrow-actions { order: 5; justify-self: start; }
  /* no `order` for the panel any more: it floats (position:absolute), so it
   * is out of grid flow and orders nothing. It does have to follow its
   * toggle, which moves to the LEFT of the row in this layout, so flip the
   * menu's anchor to match. */
  .po-jrow-actions-panel.show { left: 18px; right: auto; }
}

/* Update banner (1.1.001). Shown at the top of every app screen to anyone
 * who holds 'update_plugins' when WordPress is offering a new PaintClarity.
 * It is here rather than only on wp-admin > Plugins because the owner works
 * on the front-end app page and may never open that screen; a test install
 * was found sitting six weeks behind for exactly that reason.
 *
 * Structural, not alarming: this is not an error, so it takes the navy rail
 * treatment used elsewhere for the app's own chrome rather than the red or
 * amber of .paintops-notice. Tokens carry fallbacks because the eight themes
 * define different palettes and a theme that predates these variables must
 * still render a readable row. */
.po-update-banner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--po-s2, 8px) var(--po-s4, 16px);
  margin: 0 0 var(--po-s5, 24px);
  padding: var(--po-s3, 12px) var(--po-s4, 16px);
  background: var(--po-card-bg, #ffffff);
  border: 1px solid var(--po-card-border, #d7e3ec);
  border-radius: var(--po-radius-xs, 4px);
  font-size: var(--po-fs-body, 14px);
  line-height: 1.45;
  color: var(--po-text, #1f2733);
}
/* Blocked-channel variant. Amber, not red: nothing is broken right now, but
   the site has stopped receiving fixes and that gets worse with time. */
.po-update-banner.is-blocked { background: #fdf4e3; }
.po-update-banner.is-blocked .po-update-mark { background: #d68000; }
.po-update-banner.is-blocked .po-update-go,
.po-update-banner.is-blocked .po-update-go:hover,
.po-update-banner.is-blocked .po-update-go:focus { background: #b36a00; border-color: #b36a00; }
.po-update-mark {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--po-nav-bg, #293241);
  color: #ffffff;
  font-weight: 700;
  font-size: 13px;
  line-height: 1;
}
/* Takes the free space so the two links sit together on the right at
 * desktop widths and wrap as a pair underneath on a phone. */
.po-update-text { flex: 1 1 260px; min-width: 0; }
.po-update-text strong { font-weight: 600; }
.po-update-go,
.po-update-alt {
  flex: none;
  text-decoration: none;
  border-radius: var(--po-radius-xs, 4px);
  font-size: var(--po-fs-meta, 13px);
  font-weight: 600;
  white-space: nowrap;
}
.po-update-go {
  padding: 7px 14px;
  background: var(--po-accent, #3D5A80);
  color: var(--po-on-accent, #ffffff);
  border: 1px solid var(--po-accent, #3D5A80);
}
.po-update-go:hover,
.po-update-go:focus { background: var(--po-nav-bg, #293241); border-color: var(--po-nav-bg, #293241); color: #ffffff; }
.po-update-alt {
  padding: 7px 0;
  color: var(--po-text-muted, #5b6673);
  text-decoration: underline;
}
.po-update-alt:hover,
.po-update-alt:focus { color: var(--po-accent, #3D5A80); }
/* Dismiss is deliberately the quietest thing in the row: it is there so the
 * notice is explicitly dismissible (wordpress.org Guideline 11), not as a
 * competing action. Per version, so it returns at the next release. */
.po-update-dismiss {
  flex: none;
  padding: 7px 0;
  font-size: var(--po-fs-meta, 13px);
  color: var(--po-text-muted, #5b6673);
  text-decoration: none;
  opacity: .75;
}
.po-update-dismiss:hover,
.po-update-dismiss:focus { opacity: 1; text-decoration: underline; color: var(--po-text-muted, #5b6673); }
@media (max-width: 600px) {
  /* Phone: stack, and let the primary action be a full-width target rather
   * than a 90px tap zone beside the text. */
  .po-update-banner { gap: var(--po-s2, 8px); }
  .po-update-text { flex-basis: 100%; }
  .po-update-go { flex: 1 1 100%; text-align: center; padding: 10px 14px; }
  .po-update-alt { flex: 1 1 100%; text-align: center; }
  .po-update-dismiss { flex: 1 1 100%; text-align: center; }
}

/* Classic-layout search row (1.1.001). The sidebar shell puts the main job
 * search in its top bar; the classic app bar is an icon strip with no room
 * for an input, so it gets its own row directly above. Same component, so it
 * inherits every .po-jobs-search rule including the suggest menu. */
.po-classic-search {
  display: flex;
  justify-content: flex-end;
  margin: 0 0 var(--po-s3, 12px);
}
.po-classic-search .po-jobs-search { flex: 0 1 340px; }
@media (max-width: 782px) {
  /* Phone: full width, and it sits above the content rather than the fixed
     bottom bar, so it needs no extra clearance. */
  .po-classic-search { justify-content: stretch; }
  .po-classic-search .po-jobs-search { flex: 1 1 auto; min-width: 0; }
}

/* =====================================================================
 * COLOR VISUALIZER (includes/color-visualizer.php)
 *
 * A two-column working surface: the photo on the left at whatever size
 * the viewport allows, the controls on the right in three stacked
 * panels. Below 900px the controls drop under the photo, because on a
 * phone the painter is showing the customer the render, not tuning it.
 *
 * Every ink and surface here reads an app token with a light fallback,
 * the same shape-only convention the rest of this stylesheet uses, so
 * the themes recolor it and wp-admin still gets a readable panel. The
 * one literal color is the marker pink the JS draws the selection edge
 * in (#E6007E, a color no wall is ever painted, so the outline is never
 * mistaken for part of the render), and it lives in the JS, not here.
 * ===================================================================== */
.po-vis { margin: 0 0 var(--po-s5); }

/* The toolbar's load control is hidden until a photo is in (see the markup).
 * .paintops-btn sets its own display, and an author display beats the UA
 * sheet's [hidden], so without this the button stays on screen. Same trap as
 * .pbn-sheet[hidden] in app-nav.css. */
.po-vis-bar .paintops-btn[hidden] { display: none; }

.po-vis-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

.po-vis-bar {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--po-s2); margin-bottom: var(--po-s4);
}
.po-vis-gap { flex: 1 1 auto; }

.po-vis-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--po-s5);
  align-items: start;
}
@media (max-width: 900px) {
  .po-vis-grid { grid-template-columns: minmax(0, 1fr); }

  /* PHONE STACKING ORDER (Mike 2026-09-19). One column, so the rail falls
   * below the photo. Left alone the order would be photo, tolerance, the
   * surfaces list, and only then Color, which buries the control a painter
   * reaches for most while trying options in front of a customer.
   *
   * display:contents dissolves the wrapper so its children become grid items
   * in their own right and can be ordered against the rail. The wrapper is a
   * plain div with no semantics, so nothing is lost by removing its box.
   * If a browser ignores display:contents the layout simply falls back to
   * source order, which is the old behavior and still usable. */
  .po-vis-stagewrap { display: contents; }
  .po-vis-stage      { order: 1; }
  .po-vis-stagenote  { order: 2; }
  /* Tolerance ahead of the hint, matching the source order (2026-09-20).
   * These two must move together: source order governs above 900px and
   * `order` governs below it, so changing one alone makes the same page
   * read differently on a phone than on a laptop. */
  .po-vis-panel-tol  { order: 3; }
  .po-vis-panel-brush { order: 4; }
  .po-vis-hint       { order: 4; }
  .po-vis-side       { order: 5; }
  .po-vis-panel-list { order: 6; }
}

/* The tolerance panel under the photo. Same card as the rail panels, but it
 * sits full width under the canvas, so the slider gets the whole width to
 * work with instead of 300px. */
.po-vis-panel-tol { margin-top: var(--po-s3, 10px); }
.po-vis-panel-brush { margin-top: var(--po-s3, 10px); }
.po-vis-panel-brush[hidden] { display: none !important; }
.po-vis-brushnote { font-size: 12px; color: var(--po-text-muted, #6b7684); margin-top: 6px; line-height: 1.45; }
.po-vis-panel-tol input[type="range"] { width: 100%; }

.po-vis-stagewrap { min-width: 0; }
.po-vis-stage {
  position: relative;
  border: 1px solid var(--po-card-border, #dfe3e8);
  border-radius: 10px;
  background: var(--po-tile-bg, #f2f4f6);
  overflow: hidden;
  line-height: 0;
}
/* width:auto with a max on BOTH axes, not width:100%: a portrait phone
 * photo otherwise grows the stage to something like 1.3x the viewport and
 * pushes the whole panel column off the screen. object-fit would letterbox
 * it in place, but then the element's box no longer matches the drawn
 * image and every click lands on the wrong pixel, since the click math
 * reads getBoundingClientRect(). Sizing the element itself keeps the box
 * and the picture the same rectangle. */
.po-vis-stage canvas {
  display: block;
  margin: 0 auto;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 72vh;
  cursor: crosshair;
  touch-action: manipulation;
}
.po-vis-stage.is-empty canvas { visibility: hidden; }
.po-vis-stage canvas.is-busy { cursor: progress; }
.po-vis-stage canvas:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: -2px; }

.po-vis-placeholder {
  position: absolute; inset: 0;
  display: none;
  align-items: center; justify-content: center;
  margin: 0; padding: var(--po-s5);
  text-align: center; line-height: 1.5;
  font-size: var(--po-fs-body);
  color: var(--po-text-soft, #5b646d);
}
.po-vis-stage.is-empty .po-vis-placeholder { display: flex; }

/* THE EMPTY STAGE IS A LOAD TARGET (Mike 2026-09-20). It is the biggest
 * thing on the screen and it was inert; every load meant travelling back to
 * a small button in the toolbar, which on a phone is a scroll away. */
.po-vis-stage.is-empty .po-vis-placeholder { cursor: pointer; line-height: 1.5; }
.po-vis-stage.is-empty .po-vis-placeholder:hover { background: var(--po-card-bg, #fff); }
.po-vis-placeholder.is-over {
  background: var(--po-card-bg, #fff);
  outline: 2px dashed var(--po-accent, #437bab);
  outline-offset: -10px;
}
.po-vis-drop-inner { display: flex; flex-direction: column; align-items: center; gap: 10px; max-width: 420px; }
.po-vis-drop-lead { display: block; }
.po-vis-drop-btns { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.po-vis-drop-hint { font-size: 12.5px; color: var(--po-text-muted, #9aa4af); }

/* "Take a Photo" only where a camera is the likely source. capture= is
 * ignored on a desktop, so the button would open a file dialog under a label
 * promising a camera. Gated on the pointer, not on a browser sniff. */
.po-vis-cam { display: none; }
@media (pointer: coarse) {
  .po-vis-cam { display: inline-flex; }
  /* With a camera button present the drag hint is noise: you cannot drag a
   * file onto a phone. */
  .po-vis-drop-hint { display: none; }
}

/* THE PHOTO USED TO BE STICKY HERE, AND IT IS NOT ANY MORE (Mike 2026-09-15,
 * same day, both directions). It was added because the tolerance slider sat
 * at y963, below the fold, so reaching the control you drag most scrolled the
 * picture off the top.
 *
 * REORDERING THE RAIL REMOVED THE REASON. With Surface above Color and the
 * wheel collapsed behind its swatch, the photo, tolerance, texture, mode
 * buttons, type, hex and paint code are ALL above the fold at 1920x1080,
 * 1512x820, 1400x900, 1366x768 and 1280x800. The only thing below it is the
 * surfaces list. So sticky never engaged during the actual work; it engaged
 * only while scrolling the list, which is a large image held still with
 * content sliding past it, and that is what makes people queasy. Measured
 * the cost of dropping it too: with the list scrolled to the top of the
 * screen, 55 to 65 per cent of the photo is still visible anyway, because
 * the list sits directly under it.
 *
 * DO NOT PUT IT BACK WITHOUT MEASURING FIRST. If a future change pushes a
 * frequently used control below the fold again, fix THAT, because that is
 * the actual problem sticky was papering over. Two traps if it ever does
 * come back: the grid must align-items: stretch, or the left column ends at
 * its own content and the photo unsticks as soon as the taller right column
 * carries on past it; and print media satisfies a min-width query, so any
 * sticky rule here has to be cancelled for print or it splits the photo
 * across the sheet. */
@media (min-width: 901px) {
  .po-vis-grid { align-items: start; }
  .po-vis-side { align-content: start; }
}

.po-vis-stagenote {
  margin-top: var(--po-s2);
  font-size: var(--po-fs-meta);
  color: var(--po-text-soft, #5b646d);
  word-break: break-word;
}

.po-vis-hint {
  margin-top: var(--po-s3);
  padding: var(--po-s3) var(--po-s4);
  border: 0;
  border-radius: 6px;
  background: var(--po-tile-bg, #f6f8fa);
  font-size: var(--po-fs-body);
  line-height: 1.55;
  color: var(--po-text, #2f363d);
}

.po-vis-side { display: grid; gap: var(--po-s4); min-width: 0; }

.po-vis-panel {
  padding: var(--po-s4);
  border: 1px solid var(--po-card-border, #dfe3e8);
  border-radius: 10px;
  background: var(--po-card-bg, #fff);
  box-shadow: var(--po-card-shadow, 0 1px 2px rgba(16, 24, 40, .05));
}
.po-vis-h {
  margin: 0 0 var(--po-s3);
  font-size: var(--po-fs-label);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--po-text-soft, #5b646d);
}
.po-vis-panel label {
  display: block;
  margin: var(--po-s3) 0 var(--po-s1);
  font-size: var(--po-fs-meta);
  font-weight: 600;
  color: var(--po-text, #2f363d);
}
.po-vis-panel input[type="text"],
.po-vis-panel select {
  width: 100%;
  box-sizing: border-box;
  padding: 7px 9px;
  border: 1px solid var(--po-input-border, #dfe3e8);
  border-radius: 6px;
  font-size: var(--po-fs-body);
  background: var(--po-card-bg, #fff);
  color: var(--po-text, #1f2429);
}
.po-vis-panel input[type="range"] { width: 100%; margin: 0; accent-color: var(--po-accent, #437bab); }

.po-vis-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--po-s2); margin-bottom: var(--po-s1);
}
.po-vis-row-gap { margin-top: var(--po-s4); }
.po-vis-lab { font-size: var(--po-fs-meta); font-weight: 600; color: var(--po-text-soft, #5b646d); }
.po-vis-val { font-size: var(--po-fs-meta); font-variant-numeric: tabular-nums; color: var(--po-text, #2f363d); }

/* The wheel. The dot is absolutely positioned by the JS in CSS pixels
 * against the wrapper, so the wrapper must be exactly the wheel's box. */
.po-vis-wheelwrap { position: relative; width: 100%; max-width: 236px; margin: 0 auto var(--po-s3); }
.po-vis-wheelwrap canvas { display: block; width: 100%; height: auto; border-radius: 50%; cursor: crosshair; touch-action: none; }
#poVisWheelDot {
  position: absolute; width: 16px; height: 16px;
  margin: -8px 0 0 -8px;
  border: 2px solid #fff; border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(16, 24, 40, .35);
  pointer-events: none;
}

.po-vis-hexrow { display: flex; align-items: flex-end; gap: var(--po-s3); margin-top: var(--po-s3); }
.po-vis-swatch {
  flex: 0 0 auto; width: 44px; height: 44px;
  border: 1px solid var(--po-card-border, #dfe3e8); border-radius: 6px;
}
.po-vis-hexfield { flex: 1 1 auto; min-width: 0; }
.po-vis-hexfield label { margin-top: 0; }
#poVisHex { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* Segmented control for the two selection modes. */
.po-vis-seg {
  display: flex; margin-top: var(--po-s3); margin-bottom: var(--po-s3);
  border: 1px solid var(--po-card-border, #dfe3e8); border-radius: 6px; overflow: hidden;
}
.po-vis-segbtn {
  /* flex-basis is AUTO, not 0. With a 0 basis the three buttons take an
   * equal third each regardless of their labels, so "Similar color" was
   * squeezed into the same width as "Box" and the row read as one cramped
   * blob. Sizing from the label and sharing only the leftover space fits
   * all three at normal padding. */
  flex: 1 1 auto; padding: 7px 10px;
  border: 0; background: var(--po-card-bg, #fff); cursor: pointer;
  font-size: var(--po-fs-meta); font-weight: 600; color: var(--po-text-soft, #5b646d);
  /* "Similar color" wrapped onto two lines inside its own button in the
   * control bar and made the whole row look broken */
  white-space: nowrap;
}
.po-vis-segbtn + .po-vis-segbtn { border-left: 1px solid var(--po-card-border, #dfe3e8); }
.po-vis-segbtn.is-on { background: var(--po-accent, #437bab); color: var(--po-on-accent, #fff); }
/* Three across in a 300px rail is tight. The auto basis above does most of
 * the work; this just trims the padding a little further so the widest
 * label still never wraps. */
.po-vis-seg-3 .po-vis-segbtn { padding-left: 6px; padding-right: 6px; }

/* When the runaway guard tightens the selection it moves the tolerance the
 * painter did not touch. The hint says so in words, but the NUMBER changing
 * with nothing connecting it to the message is what unsettles people, so it
 * flashes once. Animation only, no layout shift, and it is decorative: the
 * hint remains the actual explanation for anyone who cannot see this. */
@keyframes po-vis-bump {
  0%   { background: var(--po-accent, #437bab); color: var(--po-on-accent, #fff); }
  100% { background: transparent; }
}
.po-vis-val.is-bumped {
  border-radius: 4px; padding: 0 4px; margin: 0 -4px;
  animation: po-vis-bump 1.1s ease-out 1;
}
@media (prefers-reduced-motion: reduce) {
  .po-vis-val.is-bumped { animation: none; }
}

/* A tolerance means nothing to a dragged box, so the row says so instead
 * of sitting there looking live. */
.po-vis-row.is-off, .po-vis-row.is-off + input[type="range"] { opacity: .45; }
.po-vis-panel input[type="range"]:disabled { cursor: not-allowed; }

/* The box tool's rubber band. A plain div over the stage: drawing it onto
 * the canvas would mean re-running the recolor on every pointermove just
 * to clear the previous frame. */
.po-vis-band {
  position: absolute;
  z-index: 2;
  pointer-events: none;
  border: 1.5px dashed #E6007E;
  background: rgba(230, 0, 126, .10);
}
.po-vis-band.is-cut {
  border-color: #ffffff;
  background: rgba(16, 24, 40, .28);
  box-shadow: 0 0 0 1px rgba(16, 24, 40, .45);
}
.po-vis-stage canvas.is-boxing { cursor: crosshair; touch-action: none; }

/* The list of selected surfaces. */
.po-vis-list { display: grid; gap: var(--po-s2); }
.po-vis-empty { margin: 0; font-size: var(--po-fs-meta); color: var(--po-text-soft, #5b646d); }
.po-vis-surf {
  display: flex; align-items: center; gap: var(--po-s2);
  padding: var(--po-s2);
  border: 1px solid var(--po-card-border, #dfe3e8); border-radius: 8px;
  cursor: pointer;
}
.po-vis-surf.is-active { border-color: var(--po-accent, #437bab); box-shadow: inset 0 0 0 1px var(--po-accent, #437bab); }
.po-vis-surf:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: 1px; }
.po-vis-chip { flex: 0 0 auto; width: 26px; height: 26px; border: 1px solid rgba(16, 24, 40, .18); border-radius: 5px; }
.po-vis-meta { flex: 1 1 auto; min-width: 0; display: grid; }
.po-vis-nm { font-size: var(--po-fs-meta); font-weight: 600; color: var(--po-text, #2f363d); }
.po-vis-sub {
  font-size: var(--po-fs-label); color: var(--po-text-soft, #5b646d);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.po-vis-kill {
  flex: 0 0 auto; width: 26px; height: 26px; padding: 0;
  border: 0; border-radius: 5px; background: transparent; cursor: pointer;
  font-size: 17px; line-height: 1; color: var(--po-text-soft, #5b646d);
}
.po-vis-kill:hover { background: var(--po-hover, #f0f2f4); color: var(--po-text, #1f2429); }

.po-vis-actions { display: flex; flex-wrap: wrap; gap: var(--po-s2); margin-top: var(--po-s3); }

/* The surfaces panel lives under the photo, where it is wide enough to lay
 * the rows out in a grid rather than a single stacked column. */
.po-vis-panel-list { margin-top: var(--po-s3); }
.po-vis-panel-list .po-vis-list {
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}

/* Sits under the color list and PRINTS. The render goes to the customer
 * next to a quote and a screen color is not a color match, so this line
 * has to survive onto the sheet. Never give it .no-print. */
.po-vis-disclaimer {
  margin: var(--po-s3) 0 0;
  font-size: var(--po-fs-label);
  line-height: 1.45;
  color: var(--po-text-soft, #5b646d);
  border-top: 1px solid var(--po-card-border, #dfe3e8);
  padding-top: var(--po-s2);
}

/* The collapsed colour control: swatch button plus hex on one line. The
 * button is the disclosure for the wheel below it. */
.po-vis-colorbar { display: flex; align-items: flex-end; gap: var(--po-s2); }
.po-vis-swatchbtn {
  flex: 0 0 auto; display: flex; align-items: center; gap: 7px;
  padding: 5px 9px 5px 5px;
  border: 1px solid var(--po-card-border, #dfe3e8); border-radius: 6px;
  background: var(--po-card-bg, #fff); cursor: pointer;
  font-size: var(--po-fs-meta); font-weight: 600; color: var(--po-text-soft, #5b646d);
}
.po-vis-swatchbtn:hover { background: var(--po-hover, #f0f2f4); }
.po-vis-swatchbtn:focus-visible { outline: 2px solid var(--po-accent, #437bab); outline-offset: 1px; }
.po-vis-colorbar .po-vis-swatch { width: 24px; height: 24px; border-radius: 4px; }
.po-vis-caret {
  width: 0; height: 0; margin-left: 1px;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform .15s ease;
}
.po-vis-swatchbtn[aria-expanded="true"] .po-vis-caret { transform: rotate(180deg); }
.po-vis-colorbar .po-vis-hexfield { flex: 1 1 auto; min-width: 0; }
.po-vis-wheelbox { margin-top: var(--po-s3); }

/* The sheet header is a DOCUMENT header: the painter is already looking at
 * their own app and does not need their letterhead on it, so it exists in
 * the markup only to be printed. Kept out of the flow rather than removed,
 * because the script writes the Prepared for line into it as it is typed. */
.po-vis-sheet { display: none; }

/* The panel heading is working language for the painter and document
 * language for the customer. Two real strings rather than a CSS content
 * swap, so both are readable text to a screen reader and to search. */
.po-vis-h-sheet { display: none; }

/* The Prepared for field is a normal app field on screen; on paper only its
 * value survives, up in the header. */
.po-vis-forrow { margin: 0 0 var(--po-s3); }
.po-vis-forrow label {
  display: block; margin-bottom: 4px;
  font-size: var(--po-fs-label); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--po-text-soft, #5b646d);
}
.po-vis-forrow input {
  width: 100%; box-sizing: border-box; padding: 7px 9px;
  border: 1px solid var(--po-input-border, #dfe3e8); border-radius: 6px;
  background: var(--po-input-bg, #fff); color: var(--po-text, #1f2429);
  font-size: var(--po-fs-body, 14px);
}

/* The three parts of a surface line. On screen they run together as one
 * muted line, exactly as the single string used to read; the print sheet
 * pulls them apart into columns. */
.po-vis-sub > span + span::before { content: '  \00b7  '; }

/* Printing the render is how this reaches the customer, so the print
 * sheet is the photo and the color list and nothing else. */
@media print {
  .po-vis .no-print { display: none !important; }
  .po-vis-grid { display: block; }
  .po-vis-stage { border: 0; background: none; }
  .po-vis-stage canvas { max-height: none; }
  .po-vis-side { display: block; margin-top: 12pt; }
  .po-vis-panel { border: 0; box-shadow: none; padding: 0; }
  .po-vis-surf { break-inside: avoid; }
  .po-vis-disclaimer { color: #000; border-top: 1pt solid #999; break-inside: avoid; }

  /* THE APP IS NOT PART OF THE SHEET. Everything below is scoped to
   * .po-vis-printing, a class the visualizer's own beforeprint hook puts on
   * <body> and takes off again on afterprint. The scope matters: unscoped,
   * these would strip the chrome off every other page's printout too, and
   * invoices, quotes and the expense and profit reports already carry their
   * own print treatment. Ctrl+P fires beforeprint as well, so this covers a
   * browser print, not only the Print / PDF button. */
  .po-vis-printing .po-side,
  .po-vis-printing .po-topbar,
  .po-vis-printing .paintops-appbar,
  .po-vis-printing .paintops-bottomnav,
  .po-vis-printing .po-page-head,
  .po-vis-printing .po-vis-stagenote,
  .po-vis-printing .po-vis-kill { display: none !important; }

  /* The sidebar's GRID COLUMN has to go with the sidebar. Hiding .po-side
   * alone leaves a 240px empty track and the photo keeps printing into
   * about 70 per cent of the page, which is the clipping in the PDF. */
  .po-vis-printing .po-shell {
    display: block !important;
    min-height: 0 !important;
    background: none !important;
  }
  .po-vis-printing .po-shell-main { display: block !important; }
  .po-vis-printing .paintops-app-canvas-inner { padding: 0 !important; margin: 0 !important; max-width: none !important; }

  /* The app sits on a tinted canvas on screen. On paper that is a full-page
   * flood of pale blue, so everything behind the sheet goes white. */
  .po-vis-printing,
  .po-vis-printing .po-shell,
  .po-vis-printing .po-shell-main,
  .po-vis-printing .paintops-app-canvas-inner,
  .po-vis-printing .po-vis-panel-list { background: #fff !important; }

  /* Zeroing the app gutters above puts the render hard against the paper
   * edge when the print dialog is set to no margins, which is how a PDF
   * export usually comes out. This is the sheet's own margin and it does
   * not depend on the dialog. */
  .po-vis-printing .po-vis { padding: 0.25in !important; }

  /* Paper does not scroll, so the stage is pinned to nothing here. This is
   * belt and braces now: the sticky rule it was written to beat has been
   * removed. IT IS KEPT ON PURPOSE. Print media satisfies a min-width query,
   * so anyone reintroducing a sticky stage would silently split the photo
   * across the sheet again, and this catches that before the customer does. */
  .po-vis-stage { position: static !important; top: auto !important; }

  /* The render gets the full width of the sheet. */
  .po-vis-printing .po-vis-stage canvas { width: 100% !important; height: auto !important; }

  /* Which surface is selected is the painter's business, not the customer's. */
  .po-vis-printing .po-vis-surf.is-active {
    border-color: var(--po-card-border, #dfe3e8) !important;
    box-shadow: none !important;
  }

  /* ---- the sheet as a document ---------------------------------------- */

  /* Letterhead, then the title and date, then who it is for. */
  .po-vis-printing .po-vis-sheet {
    display: flex !important;
    align-items: flex-start;
    justify-content: space-between;
    gap: 24pt;
    padding-bottom: 8pt;
    margin-bottom: 12pt;
    border-bottom: 1pt solid #999;
    color: #000;
  }
  .po-vis-printing .po-vis-sheet-logo { max-height: 48pt; max-width: 180pt; display: block; margin-bottom: 4pt; }
  .po-vis-printing .po-vis-sheet-name { font-size: 13pt; font-weight: 700; line-height: 1.2; }
  .po-vis-printing .po-vis-sheet-contact { font-size: 8pt; color: #444; margin-top: 2pt; }
  .po-vis-printing .po-vis-sheet-meta { text-align: right; flex: 0 0 auto; }
  .po-vis-printing .po-vis-sheet-title { font-size: 12pt; font-weight: 700; letter-spacing: .02em; }
  .po-vis-printing .po-vis-sheet-date { font-size: 8pt; color: #444; margin-top: 2pt; }
  .po-vis-printing .po-vis-sheet-forwrap { margin-top: 6pt; font-size: 9pt; }
  .po-vis-printing .po-vis-sheet-lab {
    display: block; font-size: 7pt; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: #666;
  }
  .po-vis-printing .po-vis-sheet-for { font-weight: 600; }
  .po-vis-printing .po-vis-sheet-addr { display: block; color: #444; }

  /* The color list becomes a table: one surface per full-width line, the
   * paint code leading, because the hex is a screen artifact and the code
   * is the thing a customer or a paint desk can act on. The screen layout
   * is a responsive grid of cards, which is wrong on paper. */
  .po-vis-printing .po-vis-h {
    font-size: 9pt; letter-spacing: .08em; color: #000; margin: 0 0 4pt;
  }
  .po-vis-printing .po-vis-h-screen { display: none; }
  .po-vis-printing .po-vis-h-sheet { display: inline; }
  .po-vis-printing .po-vis-list {
    display: block !important;
    border-top: 1pt solid #999;
  }
  .po-vis-printing .po-vis-surf {
    display: flex !important;
    align-items: baseline;
    gap: 10pt;
    width: auto !important;
    margin: 0 !important;
    padding: 5pt 0 !important;
    border: 0 !important;
    border-bottom: 0.5pt solid #ccc !important;
    border-radius: 0 !important;
    background: none !important;
  }
  .po-vis-printing .po-vis-chip {
    flex: 0 0 auto; width: 16pt; height: 16pt;
    border: 0.5pt solid #999 !important; border-radius: 2pt;
    align-self: center;
  }
  .po-vis-printing .po-vis-meta { display: flex !important; align-items: baseline; gap: 10pt; flex: 1 1 auto; min-width: 0; }
  .po-vis-printing .po-vis-nm { flex: 0 0 34%; font-size: 10pt; font-weight: 600; color: #000; white-space: normal; }
  .po-vis-printing .po-vis-sub {
    flex: 1 1 auto; display: flex !important; align-items: baseline; gap: 10pt;
    font-size: 9pt; color: #000; overflow: visible !important; white-space: normal !important;
  }
  /* the screen runs these together with a middot; on paper they are columns */
  .po-vis-printing .po-vis-sub > span + span::before { content: '' !important; }
  .po-vis-printing .po-vis-code { flex: 1 1 auto; font-weight: 600; }
  .po-vis-printing .po-vis-hexval { flex: 0 0 auto; color: #555; font-variant-numeric: tabular-nums; }
  .po-vis-printing .po-vis-areas { flex: 0 0 auto; color: #555; }

  .po-vis-printing .po-vis-disclaimer { font-size: 8pt; margin-top: 10pt; }
}


/* ============================================================
 * UNDERLINED LINKS IN THE CONTENT AREA
 *
 * Mike, 2026-09-15: "Links are hidden throughout. It's not fun guessing
 * what to click on. Please make links underline that are not in a menu or
 * a submenu." Platform wide.
 *
 * Scoped to .paintops-app-canvas-inner, and that scope does the heavy
 * lifting for free: the sidebar and the top bar are rendered BEFORE that
 * wrapper opens (see paintops.php), so the app's own chrome sits outside
 * it and is untouched without having to be listed. The wrapper exists in
 * both the shell and the classic layout, so this covers every app screen.
 *
 * The exclusions below are one idea: a thing that already announces itself
 * as a control does not need an underline to be findable. Menus and
 * sub-menus are Mike's own exemption; buttons, tiles, tabs, pills, icon
 * controls and whole-row link targets are here for the same reason.
 * Each is stated with two classes so it outranks the rule above
 * regardless of source order.
 * ============================================================ */
.paintops-app-canvas-inner a {
  text-decoration: underline;
  text-underline-offset: 0.16em;
}

/* Menus and sub-menus. */
.paintops-app-canvas-inner .pv-more-actions a,
.paintops-app-canvas-inner .pv-dir-menu a,
.paintops-app-canvas-inner .po-menu-overlay a,
.paintops-app-canvas-inner .po-js-item,
.paintops-app-canvas-inner .pbn-sheet a,
.paintops-app-canvas-inner .pab-panel a,
.paintops-app-canvas-inner .po-new-menu a,
/* Buttons, including the links wearing a button's clothes. */
.paintops-app-canvas-inner .paintops-btn,
.paintops-app-canvas-inner .button,
.paintops-app-canvas-inner .button-primary,
.paintops-app-canvas-inner .button-secondary,
.paintops-app-canvas-inner .po-new-btn,
.paintops-app-canvas-inner .po-update-go,
.paintops-app-canvas-inner .po-update-alt,
/* Quick-action TILES. Deliberately not plain .pv-quick-actions: that
 * container also holds ordinary text links on some screens (the job form's
 * "Copy Title & Description From a Past Job"), and only the tile grid has
 * a face of its own. */
.paintops-app-canvas-inner .pv-quick-actions-tiles a,
.paintops-app-canvas-inner .po-act-link,
.paintops-app-canvas-inner .pv-dir-main,
.paintops-app-canvas-inner .cs-addexpense-action,
/* Tabs, pills and segmented controls. */
.paintops-app-canvas-inner .pab-item,
.paintops-app-canvas-inner .po-biz-yeartab,
.paintops-app-canvas-inner .paintops-pipeline-tag,
.paintops-app-canvas-inner .paintops-pipeline-tag-link,
.paintops-app-canvas-inner .paintops-pipeline-view,
.paintops-app-canvas-inner .po-contact-seg a,
/* Icon-only controls: there is no word to underline. */
.paintops-app-canvas-inner .calendar-add-job,
.paintops-app-canvas-inner .calendar-nav-arrow,
.paintops-app-canvas-inner .paintops-modal-close,
.paintops-app-canvas-inner .po-cust-addr-edit,
/* Whole card or whole row is the link, so nothing has to be hunted for. */
.paintops-app-canvas-inner .po-reach-act,
.paintops-app-canvas-inner .po-reqperf-teaser,
.paintops-app-canvas-inner .paintops-pipeline-cardlink,
.paintops-app-canvas-inner .paintops-pipeline-card-title a,
.paintops-app-canvas-inner .rl-resource-list li > a,
/* The expense summary's stream tiles and month rows: the whole tile and the
 * whole month row are the link, and underlining them drags a line under the
 * dollar figure, the share and the entry count all at once. */
.paintops-app-canvas-inner .po-exsum-tile,
.paintops-app-canvas-inner .po-exsum-monthhead,
/* The tool row on the quote form underlines its LABEL only, below, so the
 * muted description beside it is not dragged under the line too. */
.paintops-app-canvas-inner .paintops-tool-card,
/* The app's own chrome, listed as belt and braces in case a future layout
 * ever renders it inside the canvas. */
.paintops-app-canvas-inner .po-side a,
.paintops-app-canvas-inner .po-topbar a,
.paintops-app-canvas-inner .pbn-item,
.paintops-app-canvas-inner .pbn-center {
  text-decoration: none;
}

/* Content links that had their underline explicitly removed by an older
 * rule more specific than the one above. These are ordinary text links, so
 * they are put back. */
.paintops-app-canvas-inner .po-jrows-foot a,
.paintops-app-canvas-inner .po-jrows-foot button,
.paintops-app-canvas-inner .paintops-back-link,
.paintops-app-canvas-inner .job-meta a,
.paintops-app-canvas-inner .pv-team-contact,
.paintops-app-canvas-inner .veh-vehicle-prompt a,
.paintops-app-canvas-inner .rl-ai-altlink,
.paintops-app-canvas-inner .paintops-tool-card .tool-card-title,
.paintops-app-canvas-inner .cs-print-profit-report {
  text-decoration: underline;
  text-underline-offset: 0.16em;
}

/* This one keeps its DOTTED underline: it opens a panel in place rather
 * than going anywhere, and the dotted line has been how this app says that
 * since the rule was written. It was being flattened to nothing by the
 * .pv-quick-actions row it sits in, so it is restated here. */
.paintops-app-canvas-inner .job-form-toggle {
  text-decoration: underline dotted;
  text-underline-offset: 0.16em;
}

/* Not on paper. Invoices, quotes and the profit report all print from
 * inside this wrapper, and a printed document should not be scored with
 * underlines under every address and reference. */
@media print {
  .paintops-app-canvas-inner a,
  .paintops-app-canvas-inner .po-jrows-foot a,
  .paintops-app-canvas-inner .paintops-back-link,
  .paintops-app-canvas-inner .job-meta a,
  .paintops-app-canvas-inner .pv-team-contact,
  .paintops-app-canvas-inner .paintops-tool-card .tool-card-title {
    text-decoration: none;
  }
}

/* ------------------------------------------------------------------
 * Job Pulse + After Action Review (?jobPulse=ID).
 *
 * Deliberately built out of the app's existing stat-tile and card
 * vocabulary (.po-biz-tile, .cs-fin-section) rather than a new look:
 * this page shows the same money as the Financials page, so it should
 * not feel like a different product. Unused in the free build, where
 * the whole feature is stripped.
 * ---------------------------------------------------------------- */
.po-pulse { margin: 0 0 24px; }

.po-pulse-links { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 16px; }

.po-pulse-section { margin: 0 0 22px; }
.po-pulse-section > h2 {
  margin: 0 0 10px;
  font-size: 16px;
  font-weight: 800;
  color: var(--po-heading, #293241);
}

/* The one-line verdict at the top of each screen. Tinted, not shouted:
 * the numbers below it are the point, this just says which of the four
 * situations the reader is in. */
.po-pulse-banner {
  border: 1px solid var(--po-card-border, #e3e8ee);
  border-radius: var(--po-radius, 12px);
  background: var(--po-card-bg, #fff);
  color: var(--po-text, #2b3440);
  padding: 12px 16px;
  margin: 0 0 14px;
  font-size: 14.5px;
}
.po-pulse-banner p { margin: 0; }
.po-pulse-banner p + p { margin-top: 6px; }
.po-pulse-banner.is-good { background: #e6f6ec; color: #16532f; }
.po-pulse-banner.is-warn { background: #fdf4e3; color: #6d4a06; }
.po-pulse-banner.is-bad  { background: #fbeceb; color: #7a1d17; }

/* auto-fit, not a fixed 4-up like .po-biz-tiles: these screens carry
 * four, five or six tiles depending on what the job actually supports,
 * and a fixed four columns leaves a fifth tile stranded alone on its own
 * row. The 180px floor keeps five tiles on one row at app width. */
/* Completion date, editable in place inside the AAR banner. A native
 * <details>: no script, and if styles never load it is still a labeled
 * form. The summary is styled as the link it reads as, and the marker is
 * removed because the date itself is the affordance. */
.po-pulse-datedit { display: inline; }
.po-pulse-datedit > summary {
  display: inline;
  cursor: pointer;
  list-style: none;
}
/* Only the date reads as the control; the lead-in word is just the
 * sentence it sits in. */
.po-pulse-datelink {
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 0.16em;
}
.po-pulse-datedit > summary::-webkit-details-marker { display: none; }
.po-pulse-datedit > summary::marker { content: ""; }
.po-pulse-datedit > summary:focus-visible { outline: 2px solid var(--po-accent, #3D5A80); outline-offset: 2px; border-radius: 3px; }
.po-pulse-datestatic { font-weight: 700; }
.po-pulse-dateform {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 10px 0 2px;
  padding: 12px 14px;
  border: 1px solid var(--po-card-border, #e3e8ee);
  border-radius: var(--po-radius, 12px);
  background: var(--po-card-bg, #fff);
  color: var(--po-text, #2b3440);
}
.po-pulse-dateform label { font-weight: 600; font-size: 13.5px; margin: 0; }
.po-pulse-dateform input[type="date"] { margin: 0; }
.po-pulse-datestatus { font-size: 13px; color: var(--po-text-muted, #6b7684); }
.po-pulse-dateform-note {
  flex: 1 1 100%;
  font-size: 12.5px;
  color: var(--po-text-muted, #6b7684);
  line-height: 1.5;
}

.po-pulse-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
  margin: 0 0 12px;
}
.po-pulse-tile {
  background: var(--po-card-bg, #fff);
  border: 1px solid var(--po-card-border, #e3e8ee);
  border-radius: var(--po-radius, 12px);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
}
.po-pulse-tile-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--po-text-muted, #6b7684);
}
/* Same 22px/600 tabular figure as .po-biz-tile-value, so a number on
 * this page and the same number on the Business page look like the same
 * kind of thing. */
.po-pulse-tile-value {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--po-heading, #293241);
  margin-top: var(--po-s1, 4px);
  font-variant-numeric: tabular-nums;
}
.po-pulse-tile-note { font-size: 12.5px; color: var(--po-text-muted, #6b7684); margin-top: 2px; }
/* Tone colors the tile's 1px border, never the figure: a number that
 * changes color as it changes value is hard to read across a row of tiles.
 * No wide accent bars anywhere (Mike, 2026-09-30). */
.po-pulse-tile.is-good { border-color: #1a7a43; }
.po-pulse-tile.is-warn { border-color: #a8710a; }
.po-pulse-tile.is-bad  { border-color: #a3271f; }

.po-pulse-note {
  font-size: 13px;
  color: var(--po-text-muted, #6b7684);
  margin: 8px 0 0;
  max-width: 70ch;
  line-height: 1.5;
}
.po-pulse-note.is-warn { color: #8a5a00; }

/* The Pulse tables ARE .cs-fin-table tables (same treatment, same phone
 * stacking, same automatic column heads and right-aligned money from
 * assets/js/app-fin-table-labels.js). Only what is specific to Pulse
 * lives here: the row label and the sign of a variance. */
/* The AAR's closing read-out. Sentences, not tiles: these lines are
 * conclusions, and a conclusion has to show its arithmetic before a
 * number means anything. Measure capped so they stay readable, and the
 * tone is a bar on the left rather than colored text, so the figures
 * inside the sentence keep one weight. */
.po-pulse-readout {
  margin: 4px 0 0;
  border: 1px solid var(--po-card-border, #e3e8ee);
  border-radius: var(--po-radius, 12px);
  background: var(--po-card-bg, #fff);
  max-width: 78ch;
  overflow: hidden;
}
/* One read-out line: a mark, a verdict word, then the sentence. The
 * mark and the word do the framing the bare left border was only
 * hinting at (Mike 2026-09-16). Four verdicts, including a neutral one
 * for the lines that are a statement rather than a pass or a fail. */
.po-pulse-read {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 14px 18px;
  border-bottom: 1px solid var(--po-line, #eee);
}
.po-pulse-read:last-child { border-bottom: 0; }
.po-pulse-read-mark { flex: 0 0 auto; line-height: 0; margin-top: 1px; }
.po-pulse-read-body { min-width: 0; }
.po-pulse-read-label {
  display: block;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  margin-bottom: 3px;
}
.po-pulse-read p {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.6;
}
.po-pulse-read.is-good { background: #f2faf5; }
.po-pulse-read.is-good .po-pulse-read-mark,
.po-pulse-read.is-good .po-pulse-read-label { color: #1a7a43; }
.po-pulse-read.is-warn { background: #fdf8ef; }
.po-pulse-read.is-warn .po-pulse-read-mark,
.po-pulse-read.is-warn .po-pulse-read-label { color: #8a5c07; }
.po-pulse-read.is-bad  { background: #fdf4f3; }
.po-pulse-read.is-bad .po-pulse-read-mark,
.po-pulse-read.is-bad .po-pulse-read-label { color: #a3271f; }
.po-pulse-read.is-note { }
.po-pulse-read.is-note .po-pulse-read-mark,
.po-pulse-read.is-note .po-pulse-read-label { color: var(--po-text-muted, #667581); }
.po-pulse-readout strong {
  color: var(--po-heading, #293241);
  font-variant-numeric: tabular-nums;
}

/* The day-length toggle sits inside the assumption note, as a link in
 * the sentence that explains what it changes. */
.po-pulse-basis-toggle { white-space: nowrap; }

.po-pulse-table .po-pulse-rowlabel { font-weight: 600; color: var(--po-heading, #293241); }
.po-pulse-table td.is-good { color: #1a7a43; font-weight: 600; }
.po-pulse-table td.is-bad  { color: #a3271f; font-weight: 600; }

/* Readiness list: a fact either is or is not recorded on the job, so
 * the marker is a state, not a checkbox the reader can tick. */
.po-pulse-checks { list-style: none; margin: 0; padding: 0; }
.po-pulse-checks li {
  position: relative;
  padding: 6px 0 6px 26px;
  font-size: 14px;
}
.po-pulse-checks li::before {
  position: absolute;
  left: 0;
  top: 6px;
  font-weight: 700;
}
.po-pulse-checks li.is-ok::before { content: "\2713"; color: #1a7a43; }
.po-pulse-checks li.is-open::before { content: "\2022"; color: var(--po-text-muted, #6b7684); }
.po-pulse-checks li.is-open { color: var(--po-text-muted, #6b7684); }

.po-pulse-addon-list { margin: 10px 0 0; padding: 0 0 0 18px; font-size: 14px; }
.po-pulse-addon-list li { margin: 4px 0; }
.po-pulse-flag {
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--po-text-muted, #6b7684);
  margin-left: 6px;
}

.po-pulse-ai { margin: 6px 0 16px; }
.po-pulse-ai .paintops-pulse-ai-status { margin-left: 8px; font-size: 13px; }
.po-pulse-ai .paintops-pulse-ai-result {
  margin-top: 10px;
  padding: 12px 14px;
  border: 1px solid var(--po-card-border, #e3e8ee);
  border-radius: var(--po-radius, 12px);
  background: var(--po-tile-bg, #f9fafb);
  color: var(--po-text, #2b3440);
  max-width: 70ch;
}
.po-pulse-ai .paintops-pulse-ai-result p { margin: 0 0 8px; }
.po-pulse-ai .paintops-pulse-ai-result p:last-child { margin: 0; }

@media (max-width: 900px) { .po-pulse-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) {
  .po-pulse-tiles { grid-template-columns: 1fr; }
}

@media print {
  .po-pulse-ai { display: none; }
}

/* ---------------------------------------------------------------
 * Refund rows in the Job Financials payments log (2026-09-16).
 * A refund is a negative row; the owner types a positive amount and
 * the row says what it is, so nobody has to read a minus sign to
 * understand the ledger. Tinted and tagged rather than colored red
 * alone, because a refund is a normal event, not an error state.
 * --------------------------------------------------------------- */
.cs-payment-is-refund > td { background: #fdf4e3; }
.cs-payment-refund-tag {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 7px;
    border-radius: 10px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .02em;
    text-transform: uppercase;
    color: #6d4a06;
    background: #f3e0bd;
    vertical-align: middle;
}
.cs-payment-note { width: 100%; min-width: 120px; }

/* ---------------------------------------------------------------
 * Crew availability badges on the job form (2026-09-17).
 * Two states, and they look different on purpose: time off is a
 * refusal (the row is dimmed and the box disabled), being on another
 * job is a caution (the row stays fully usable).
 * --------------------------------------------------------------- */
.po-worker-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 8px;
    padding: 4px 6px;
    border-radius: 6px;
}
.po-worker-row:last-of-type { margin-bottom: 16px; }
.po-worker-row.has-timeoff { background: #fdf4f3; opacity: .75; }
.po-worker-row.has-overlap { background: #fdf8ef; }
.po-worker-flags { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.po-worker-flag {
    font-size: 12px;
    font-weight: 600;
    line-height: 1.3;
    padding: 3px 8px;
    border-radius: 10px;
    white-space: normal;
}
.po-worker-flag.is-off { color: #8c1d16; background: #f8dcd9; }
.po-worker-flag.is-overlap { color: #6d4a06; background: #f6e4c4; }


/* ---------------------------------------------------------------------
 * Message History: the "record a contact" form (2026-09-17).
 *
 * Built as a real field grid rather than labels-with-<br> and inline
 * widths. The fields are a select, a select, a datetime-local and a text
 * box, which browsers give four different intrinsic heights and baselines,
 * so the old flex row stepped up and down and the labels sat flush on
 * their inputs. auto-fit keeps it one row on a desk and stacks it on a
 * phone with no breakpoint of its own.
 * ------------------------------------------------------------------- */
.po-comm-form { margin: 0; }
.po-comm-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--po-s3, 12px) var(--po-s4, 16px);
  margin: 0 0 var(--po-s4, 16px);
}
.po-comm-field { display: flex; flex-direction: column; gap: var(--po-s1, 4px); min-width: 0; }
.po-comm-field--wide { grid-column: 1 / -1; }
.po-comm-field > span {
  font-size: var(--po-fs-label, 11px);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--po-text-muted, #667581);
}
/* The controls are already sized by the canvas control rule above; these
 * only stop a grid cell from being widened by its own field. */
.po-comm-field select,
.po-comm-field input,
.po-comm-field textarea { width: 100%; max-width: 100%; box-sizing: border-box; }
.po-comm-field textarea { min-height: 84px; resize: vertical; }
.po-comm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--po-s3, 12px);
  align-items: center;
  margin: var(--po-s4, 16px) 0 0;
}
.po-comm-hint {
  margin: var(--po-s2, 8px) 0 var(--po-s4, 16px);
  font-size: var(--po-fs-meta, 13px);
  color: var(--po-text-muted, #667581);
}
/* One log entry. */
.po-comm-row { padding: var(--po-s3, 12px) 0; border-top: 1px solid var(--po-card-border, #e8ecf0); }
.po-comm-row:first-of-type { border-top: 0; }
.po-comm-head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--po-s2, 8px);
  align-items: baseline;
}
.po-comm-when { margin-left: auto; white-space: nowrap; font-size: var(--po-fs-meta, 13px); color: var(--po-text-muted, #667581); }
@media (max-width: 782px) {
  /* Once the head wraps, margin-left:auto leaves the date floating on a
   * line of its own pushed to the right, which reads as a stray value.
   * On a phone it becomes its own left-aligned line instead. */
  .po-comm-when { margin-left: 0; width: 100%; }
}
.po-comm-dir { font-size: var(--po-fs-meta, 13px); color: var(--po-text-muted, #667581); }
.po-comm-line { font-size: var(--po-fs-meta, 13px); margin-top: var(--po-s1, 4px); }
.po-comm-note { font-size: var(--po-fs-meta, 13px); margin-top: var(--po-s2, 8px); white-space: pre-wrap; }
.po-comm-by { font-size: 12px; color: var(--po-text-muted, #667581); margin-top: var(--po-s1, 4px); }
.po-comm-chip {
  font-size: 11px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 10px;
  white-space: nowrap;
}
.po-comm-chip.is-confirmed { background: #e4f0e6; color: #1f5b2c; }
.po-comm-chip.is-opened    { background: #f6e4c4; color: #6d4a06; }
.po-comm-chip.is-recorded  { background: #e7ecf2; color: #3a4b5c; }
.po-comm-edit {
  margin: var(--po-s3, 12px) 0 0;
  padding: var(--po-s3, 12px);
  border-radius: var(--po-radius-xs, 4px);
  background: var(--po-tile-bg, #f6f7f8);
}

/* ============================================================
 * VENDOR "DID YOU MEAN" (assets/js/vendor-suggest.js)
 *
 * Sits under the New vendor box on the receipt forms. Quiet by design:
 * it is a question, not a warning, and it appears while someone is typing
 * a name they are perfectly entitled to type.
 * ============================================================ */
.po-vendor-tip {
  margin-top: 6px;
  padding: 8px 10px;
  border: 1px solid #cfe0ee;
  border-radius: var(--po-radius-sm, 6px);
  background: #f2f8fc;
  color: #2b4a68;
  font-size: 12.5px;
  line-height: 1.5;
  max-width: 340px;
}
.po-vendor-tip[hidden] { display: none; }
.po-vendor-tip button {
  display: inline-block;
  margin: 6px 8px 0 0;
  padding: 3px 9px;
  border-radius: 999px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.po-vendor-tip-yes { border: 1px solid var(--po-accent, #437bab); background: var(--po-accent, #437bab); color: #fff; }
.po-vendor-tip-no  { border: 1px solid #cfe0ee; background: #fff; color: #4a6b85; }

/* "About this data": reference material folded out of the way. A plain
 * <details>, deliberately NOT in the pobiz exclusive group, so opening it
 * cannot shut the section it lives inside. */
.po-biz-about { margin: 0 0 14px; }
.po-biz-about > summary {
  cursor: pointer;
  display: inline-block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--po-text-muted, #6b7480);
  padding: 3px 0;
}
.po-biz-about > summary:hover { color: var(--po-accent, #437bab); }
.po-biz-about[open] > summary { margin-bottom: 6px; }
.po-biz-about-body {
  padding: 10px 14px;
  border-left: 1px solid var(--po-card-border, #dfe4e9);
  color: var(--po-text-muted, #6b7480);
  font-size: 13px;
  line-height: 1.6;
}
.po-biz-about-body p { margin: 0 0 8px; }
.po-biz-about-body p:last-child { margin-bottom: 0; }
.po-biz-about-body ul { margin: 0 0 8px; padding-left: 18px; }
.po-biz-about-body li { margin: 0 0 3px; }

/* Customer History (Mike 2026-09-29): the funnel strip above the timeline
 * and the stage rows inside it. A reached stage is filled, an unreached one
 * is an outline that says "not yet"; Declined takes the Accepted slot. The
 * stage badges reuse the comm chip shape so a payment row and a texted row
 * read as one list. */
.po-funnel { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 18px; }
.po-funnel-step { flex: 1 1 96px; min-width: 96px; border: 1px solid var(--po-card-border, #e6e6e6); border-radius: var(--po-radius-xs, 4px); padding: 8px 10px; background: var(--po-card-bg, #fff); color: var(--po-text-muted, #5b6673); }
.po-funnel-step.is-reached { border-color: var(--po-positive, #1e7e34); color: var(--po-text, #2b3440); }
.po-funnel-step.is-declined { border-color: var(--po-negative, #c0392b); color: var(--po-negative, #c0392b); }
.po-funnel-lab { display: block; font-size: var(--po-fs-label, 11px); font-weight: 700; text-transform: uppercase; letter-spacing: .07em; }
.po-funnel-when { display: block; font-size: 13px; font-variant-numeric: tabular-nums; }
.po-funnel-step.is-reached .po-funnel-when { color: var(--po-positive, #1e7e34); font-weight: 600; }
.po-comm-chip.po-comm-stage { background: var(--po-accent-soft, rgba(70,130,180,0.10)); color: var(--po-accent, #3D5A80); }
.po-comm-chip.po-comm-stage.is-paid,
.po-comm-chip.po-comm-stage.is-accepted,
.po-comm-chip.po-comm-stage.is-done { background: #e4f0e6; color: #1f5b2c; }
.po-comm-chip.po-comm-stage.is-declined { background: #f7e1df; color: #8a2a22; }
.po-comm-chip.po-comm-stage.is-opened { background: #f6e4c4; color: #6d4a06; }

/* Paintable-area prompt inside a completion notice (2026-10-03). */
.po-area-quick { display: block; margin-top: 6px; font-size: 13px; }
.po-area-quick-msg { color: inherit; }
.po-area-quick-toggle { display: inline-block; margin-left: 6px; padding: 2px 8px; font: inherit; font-size: 12.5px; color: var(--po-accent, #2f6fed); background: none; border: 1px solid currentColor; border-radius: 4px; cursor: pointer; }
.po-area-quick-panel { margin-top: 8px; }
.po-area-quick-form { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.po-area-quick-form label { font-size: 13px; font-weight: 600; }
.po-area-quick-form input[type="number"] { width: 140px; padding: 6px 8px; }

/* Calculator add-to-quote chooser and change report (2026-10-03). */
.po-calc-choose { position: fixed; inset: 0; z-index: 100000; background: rgba(20, 28, 40, .55); display: flex; align-items: center; justify-content: center; padding: 16px; }
.po-calc-choose-box { background: var(--po-card-bg, #fff); color: var(--po-text, #2b3440); border-radius: 10px; max-width: 560px; width: 100%; max-height: 90vh; overflow: auto; padding: 18px 20px; box-shadow: 0 12px 40px rgba(0, 0, 0, .25); }
.po-calc-choose-box h3 { margin: 0 0 6px; font-size: 17px; }
.po-calc-choose-intro { margin: 0 0 12px; font-size: 13px; color: var(--po-muted, #5b6676); }
.po-calc-choose-item { border: 1px solid var(--po-line, #e3e6ea); border-radius: 8px; padding: 10px 12px; margin: 0 0 12px; }
.po-calc-choose-item legend { font-weight: 600; font-size: 13.5px; padding: 0 4px; }
.po-calc-choose-item label { display: block; padding: 5px 0; font-size: 14px; cursor: pointer; }
.po-calc-choose-actions { display: flex; gap: 10px; margin: 6px 0 0; }
.po-calc-change-report { margin: 10px 0 0; padding: 10px 14px; border: 1px solid #bcd3f5; background: #f3f8ff; border-radius: 8px; font-size: 13.5px; }
.po-calc-change-report ul { margin: 0 0 6px 18px; padding: 0; }
.po-calc-change-report p { margin: 0; color: var(--po-muted, #5b6676); font-size: 12.5px; }
.po-calc-row-changed { background: #fff7d6 !important; transition: background 1.5s ease; }
