/* Portal-specific styling, layered over the copied shell (style.css + tabler-bridge.css).
   Those two files stay byte-identical to app/public/ so the eventual core extraction can
   diff them; everything the portal adds lives here.

   The claim statuses are the portal's own scale. Same rule as the Rulebook's: the word is
   the value, colour is never the only signal.
     paid       — settled                      (good / green)
     invoiced   — awaiting payment             (amber)
     claimed    — submitted to the Yard        (amber)
     claimable  — evidence ready, act of it    (blue: it asks for attention, not alarm)
     forecast   — not yet due                  (quiet grey)                                */

:root {
  --pt-claimable:    #2f6fd0;
  --pt-claimable-bg: rgba(47, 111, 208, .10);
}
[data-theme="dark"] {
  --pt-claimable:    #7aa7e8;
  --pt-claimable-bg: rgba(122, 167, 232, .14);
}

/* Grid cells (pt_col_status) — slugs of the CLIENT-facing words (ui.php: planned /
   invoice expected / invoice submitted / awaiting payment / paid). The .ag-cell prefix
   matters: the base rule in tabler-bridge is 0,2,0 — an unqualified class here loses
   and the edge never draws. */
.ag-cell.pt-status-paid {
  border-left-color: var(--led-good); background: var(--led-good-bg); color: var(--led-good);
}
.ag-cell.pt-status-awaiting-payment,
.ag-cell.pt-status-invoice-submitted {
  border-left-color: var(--annunciator); background: var(--annunciator-bg); color: var(--annunciator);
}
.ag-cell.pt-status-invoice-expected {
  border-left-color: var(--pt-claimable); background: var(--pt-claimable-bg); color: var(--pt-claimable);
}
.ag-cell.pt-status-planned,
.ag-cell.pt-status-declined,
.ag-cell.pt-status-- {
  border-left-color: var(--ink-4); background: var(--anodised); color: var(--ink-3);
}
/* Confidence tiers in the forecast grid (values Committed/Ready/Trend/Contract slug
   through the same status-cell mechanism). How sure, not how done: blue for fact and
   near-fact, amber for a trend window, quiet for the contract tail. */
.ag-cell.pt-status-committed,
.ag-cell.pt-status-ready {
  border-left-color: var(--pt-claimable); background: var(--pt-claimable-bg); color: var(--pt-claimable);
}
.ag-cell.pt-status-committed { font-weight: 650; }
.ag-cell.pt-status-trend {
  border-left-color: var(--annunciator); background: var(--annunciator-bg); color: var(--annunciator);
}
.ag-cell.pt-status-contract {
  border-left-color: var(--ink-4); background: var(--anodised); color: var(--ink-3);
}
/* Change-order approval uses the same optics: approved settles green, pending asks amber. */
.ag-cell.pt-status-approved {
  border-left-color: var(--led-good); background: var(--led-good-bg); color: var(--led-good);
}
.ag-cell.pt-status-pending {
  border-left-color: var(--annunciator); background: var(--annunciator-bg); color: var(--annunciator);
}

/* The same scale as a pill, for detail pages. */
.pt-pill {
  display: inline-block; padding: 1px 9px; border-radius: 99px;
  font-size: 12px; font-weight: 550; border: 1px solid var(--edge);
  background: var(--anodised); color: var(--ink-3);
}
.pt-pill.pt-status-paid              { background: var(--led-good-bg);   color: var(--led-good);   border-color: var(--led-good); }
.pt-pill.pt-status-awaiting-payment,
.pt-pill.pt-status-invoice-submitted { background: var(--annunciator-bg); color: var(--annunciator); border-color: var(--annunciator); }
.pt-pill.pt-status-invoice-expected  { background: var(--pt-claimable-bg); color: var(--pt-claimable); border-color: var(--pt-claimable); }

/* Confidence tiers on the forecast — how sure, not how done. Committed and Ready carry
   the portal blue (fact and near-fact), Trend asks amber attention, Contract stays quiet. */
.pt-pill.pt-tier-committed { background: var(--pt-claimable); color: #fff; border-color: var(--pt-claimable); }
.pt-pill.pt-tier-ready     { background: var(--pt-claimable-bg); color: var(--pt-claimable); border-color: var(--pt-claimable); }
.pt-pill.pt-tier-trend     { background: var(--annunciator-bg); color: var(--annunciator); border-color: var(--annunciator); }

/* Gauges */
.pt-gauge-value { font-size: 1.6rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.pt-count-failed .pt-gauge-value        { color: var(--estop, #c0392b); }
.pt-count-not_performed .pt-gauge-value { color: var(--annunciator); }

/* Facts table on the installment detail */
.pt-facts th { text-align: left; padding-right: 1rem; color: var(--ink-3); font-weight: 500; white-space: nowrap; }
.pt-facts td { padding-bottom: .25rem; }

/* Weekly trend strip */
.pt-trend-scroll { overflow-x: auto; }
.pt-trend rect { fill: var(--pt-claimable); opacity: .85; }
.pt-trend text { fill: var(--ink-4); font-size: 9px; }

/* --- The quiet system (UI strategy passes 1–3) ------------------------------------- */

/* The demo line: on every screen, one line, never an alert that shouts. */
.pt-ribbon {
  font-size: 12.5px; color: var(--annunciator);
  background: var(--annunciator-bg);
  border: 1px solid color-mix(in srgb, var(--annunciator) 30%, transparent);
  border-radius: 4px; padding: 4px 12px; margin-bottom: 14px;
}
.pt-ribbon a { color: inherit; text-decoration: underline; }

/* The corner lockup (owner, 26-08, drie rondes: "gebruik die hele hoek" → "25%
   kleiner, in het midden"). One composed block, the way a print lockup is set: the
   wordmark centred at 75% of the block, the product name tracked out to EXACTLY the
   rendered logo width by fitLockup() in layout.php (measured, self-tuning).
   No display here: tabler-bridge owns which theme-variant shows — a later
   `display: block` un-hides BOTH marks (that bug lived for one screenshot). */
.rb-brand { width: 75%; margin: 0 auto .55rem; }
.rb-brand-product {
  display: block; text-align: center;
  font-size: .6875rem; font-weight: 550;
  letter-spacing: .6em; /* fallback; layout.php measures and tunes to the logo width */
  text-transform: uppercase;
  color: var(--ink-3); line-height: 1; white-space: nowrap;
}
/* The lockup owns its corner: air above, and a clear step down to YOUR PROJECT —
   snug against the edge read as unfinished (owner, 26-08). */
.navbar-vertical .navbar-brand { padding-top: 1.7rem; padding-bottom: 1.5rem; }

/* The two top-bar dropdowns made the chip cluster wide enough to crush the title
   column into a one-character ribbon. The title keeps its room; the chips wrap BELOW
   it when the line is tight — never the other way around. */
.page-header .row > .col { min-width: 16rem; }

/* Tile labels read as sentence case: the label is the NAME of a figure, not a small-caps
   ornament — only the section heads (.pt-ledger) keep small caps, so the two layers stay
   tellable apart (DESIGN.md; harvested from taste-skill §4.7, 2026-08-25). */
.card .subheader { text-transform: none; letter-spacing: 0; font-size: 12.5px; }

/* The basis line — the ONE way a figure states where it came from. Small, grey, always
   directly under the figure. At most ~one separator dot per line: more facts become more
   LINES (pt_basis nested arrays), never a longer chain (DESIGN.md). */
.pt-basis { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.pt-basis-dot { color: var(--ink-4); }
.pt-basis-line { display: block; }

/* Grid skeleton — the shape of the coming table, never a spinner (DESIGN.md).
   Sits over the empty host and is removed by pt-grid.js on gridReady. */
.pt-skel {
  position: absolute; inset: 0; z-index: 1; overflow: hidden;
  padding: 6px 12px; pointer-events: none;
}
.pt-skel-row { display: flex; gap: 12px; align-items: center; height: 40px; }
.pt-skel-row.pt-skel-head { height: 36px; }
.pt-skel-row span {
  height: 11px; border-radius: 4px; background: var(--anodised-deep);
  animation: pt-skel-pulse 1.2s ease-in-out infinite;
}
.pt-skel-row.pt-skel-head span { background: var(--anodised); }
@keyframes pt-skel-pulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .pt-skel-row span { animation: none; opacity: .7; }
}

/* A definition, one CLICK away — hover only recolours the ⓘ itself, nothing pops
   out until it is pressed (mouse, Enter, or tap alike). */
.pt-info-wrap { position: relative; display: inline-block; }
.pt-info {
  display: inline-block; color: var(--ink-4); font-size: 12px; font-style: normal;
  cursor: pointer; vertical-align: 1px;
  border: 0; background: none; padding: 0; line-height: 1; font-family: inherit;
}
.pt-info:hover, .pt-info:focus-visible, .pt-info[aria-expanded="true"] {
  color: var(--pt-claimable); outline: none;
}
/* Colour alone is not a focus state (WIG-audit): keyboard focus gets the house ring. */
.pt-info:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--plotter) 18%, transparent);
  border-radius: 4px;
}
/* The shell's generic button styling (style.css: `button:hover { background:
   var(--action-lift) }`, press-transform, min-height) outranks a bare class — on hover
   the ⓘ grew a lifted pill, which reads as yet another popout. The ⓘ is typographic,
   not a button-shaped control: pin every state down with the element+class selector. */
button.pt-info, button.pt-info:hover, button.pt-info:active, button.pt-info:focus {
  background: none; border: 0; box-shadow: none; padding: 0; margin: 0;
  height: auto; min-height: 0; min-width: 0; transform: none; display: inline-block;
}
.pt-info-pop {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 40;
  width: min(340px, 78vw);
  background: var(--tblr-bg-surface, #fff); color: var(--ink-2);
  border: 1px solid var(--edge-soft); border-radius: 8px;
  padding: 9px 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, .14);
  /* It sits inside uppercase subheaders and bold headings: reset all of that. */
  font-size: 12.5px; font-weight: 400; line-height: 1.45;
  text-transform: none; letter-spacing: normal; text-align: left; white-space: normal;
}
.pt-info-pop.pt-pop-right { left: auto; right: 0; }

/* A clickable grid row says so itself: hover tint plus a chevron in the last cell.
   If the copy still needs the word "click", the affordance has failed. */
.grid-host .rb-row-link:hover .ag-cell { background: var(--anodised); }
.grid-host .rb-row-link .ag-cell:last-of-type::after {
  content: "›"; color: var(--ink-4); float: right; margin-left: 6px;
  opacity: 0; transition: opacity .1s ease;
}
.grid-host .rb-row-link:hover .ag-cell:last-of-type::after { opacity: 1; }

/* The overview tiles: each is a link to the data behind its number, and each can be
   dragged into the order this reader wants (pt-tiles.js). The grab cue is the cursor and
   the lift on hover — a tile covered in handles and chevrons stops being a number. */
.pt-tile {
  display: block; color: inherit; text-decoration: none;
  transition: transform .12s ease, box-shadow .12s ease;
}
/* Equal heights only where tiles sit side by side in a grid column. On a full-width
   card (the status line) a percentage height has nothing sane to resolve against, and
   one browser state rendered it viewport-tall — scope the rule to where it means
   something. */
[data-tile] > .pt-tile, .col-sm-6 > .pt-tile { height: 100%; }
/* Hover does NOTHING (owner): even the soft lift made every tile twitch as the pointer
   crossed it. At rest a tile is simply a card; the pointer cursor of the link is the
   click affordance. Movement is earned by the 400ms hold — the ARMED state lifts. */
.pt-tile:focus-visible { outline: 2px solid var(--pt-claimable); outline-offset: 2px; }
/* The grip appears when the hand hovers — the tile says "you can move me" itself. */
[data-tile] { position: relative; }
/* THE fix that makes dragging actually work on macOS: a mousedown on selectable TEXT
   inside a draggable element starts a text selection, not a drag — and these tiles are
   nearly all text, so grabbing "anywhere on the tile" silently grabbed the words
   instead. Selection off inside tiles; the data behind a tile is one click away for
   anyone who wants to copy it. */
[data-tile], [data-tile] * { -webkit-user-select: none; user-select: none; }
/* The hover lift animates transform — mid-drag that fights the browser's drag image. */
[data-tile].pt-tile-dragging .pt-tile { transition: none; transform: none; }
/* Section blocks (the chart, the status line, a whole tile row) drag too; the cursor
   says so wherever the grab lands outside an inner link — and a CARD block answers a
   hover the same way a tile does, or it reads as the one dead thing on the page. Rows
   are deliberately excluded: they would light up whenever any tile inside is hovered. */

[data-tile]::after {
  content: "⠿"; position: absolute; top: 8px; right: 12px;
  color: var(--ink-4); font-size: 11px; opacity: 0; transition: opacity .1s ease;
  pointer-events: none;
}
/* Calm at rest (owner): the grip and the grab cursor appear only once a hold has ARMED
   the tile — pages showing drag affordances everywhere read as nervous about their own
   order. Rearranging is discoverable on ?p=about and via the hold itself. */
[data-tile].pt-tile-armed::after { opacity: .9; }
[data-tile].pt-tile-armed {
  cursor: grabbing;
  outline: 2px solid var(--pt-claimable); outline-offset: 2px; border-radius: 8px;
  transform: translateY(-2px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, .12);
  transition: transform .12s ease, box-shadow .12s ease;
}
[data-tile].pt-tile-dragging { opacity: .4; }
/* The drop-target highlight sits on the target ITSELF — not on an inner .pt-tile card,
   which a plain block (the chart, a whole row) does not have and then showed nothing. */
[data-tile].pt-tile-over {
  outline: 2px dashed var(--pt-claimable); outline-offset: 3px; border-radius: 8px;
}

/* Forecast drill-downs: a week's amount is a link that unfolds the week; a month is a
   native <details> that unfolds its installments. */
.pt-week-link { color: inherit; text-decoration: none; border-bottom: 1px dotted currentColor; }
.pt-week-link:hover { border-bottom-style: solid; }
.pt-planner .pt-selected { box-shadow: inset 0 0 0 2px var(--pt-claimable); }
.pt-breakdown { border-top: 1px solid var(--edge); padding-top: .75rem; }
/* The row a ?co= link came for, in the approval card above the grid. */
.pt-focus > td { background: var(--pt-claimable-bg); }

/* Progress by area — the Artura dashboard shape: a ring per area, three measures under
   it. The ring never travels without its counts; the caption under every card carries
   them, and each bar carries its own. */
.pt-area { height: 100%; }
.pt-area-name { font-weight: 600; }
.pt-donut { flex: none; }
.pt-donut-track { stroke: var(--anodised-deep, rgba(0,0,0,.07)); }
/* One neutral hue: the ring says how much, never how well. Verdict colours (green,
   amber, red) are reserved for figures measured against a commitment. */
.pt-donut-value { stroke: var(--pt-claimable); }
.pt-donut text { fill: var(--ink-2); font-size: 11px; font-weight: 600;
  font-variant-numeric: tabular-nums; }

.pt-measure { margin-top: 6px; }
.pt-measure-track { height: 4px; border-radius: 2px; background: var(--anodised-deep, rgba(0,0,0,.07)); margin-top: 2px; }
.pt-measure-fill { height: 4px; border-radius: 2px; background: var(--pt-claimable); }
/* Categories differ by depth of the same hue, not by verdict colours — a red bar reads
   as a problem, and "equipment locations" is a kind of work, not a judgement. */
.pt-measure-fill.pt-mequipmentlocations { background: rgba(var(--pt-heat-rgb), .40); }
.pt-measure-fill.pt-mproducts { background: rgba(var(--pt-heat-rgb), .70); }
.pt-measure-fill.pt-mcables { background: var(--pt-claimable); }

/* A ledger heading: the line that keeps the two moneys apart. */
.pt-ledger {
  font-size: 13px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-3); border-bottom: 1px solid var(--edge); padding-bottom: 6px;
  margin-bottom: 14px;
}

/* A NUMBER never borrows green from being a hyperlink: an amount that is a link keeps
   ink colour with a quiet underline, so green stays a verdict, not a navigation hint. */
a.pt-money-link { color: inherit; text-decoration: underline; text-decoration-style: dotted; }
a.pt-money-link:hover { text-decoration-style: solid; }

/* The metrics strip: four numbers, each a link and draggable within the strip. Ours
   sits on the right BY POSITION, not by a class — a margin class would travel with the
   item when the reader drags it, and whatever ends up last should be the one that
   floats right. */
.pt-metric { color: inherit; text-decoration: none; white-space: nowrap; }
.pt-metric:hover b { color: var(--pt-claimable); }
.pt-metric b { font-size: 1.15rem; font-variant-numeric: tabular-nums; }
.pt-metric-strip > .pt-metric:last-child { margin-left: auto; }

/* The licence pot: one bar, budget as the frame, expectation as the fill — solid where
   it is fact (quoted/decided), tinted where it is still the index assumption. */
.pt-pot { position: relative; height: 14px; border-radius: 7px;
  background: var(--anodised-deep, rgba(0,0,0,.07)); overflow: hidden; }
.pt-pot-fill, .pt-pot-fact { position: absolute; inset: 0 auto 0 0; border-radius: 7px; }
.pt-pot-fill { background: rgba(var(--pt-heat-rgb), .35); }
.pt-pot-fact { background: var(--pt-claimable); }
.pt-good { color: var(--led-good); }
.pt-warn { color: var(--annunciator); }

/* Budget readings and states, through the same status-cell/pill machinery. Meevaller is
   the credibility purchase (§8.3) — it gets the good green, prominently. */
.ag-cell.pt-status-windfall, .pt-pill.pt-status-windfall {
  border-left-color: var(--led-good); background: var(--led-good-bg); color: var(--led-good);
}
.ag-cell.pt-status-overrun, .pt-pill.pt-status-overrun {
  border-left-color: var(--annunciator); background: var(--annunciator-bg); color: var(--annunciator);
}
.ag-cell.pt-status-on-budget,
.ag-cell.pt-status-assumption {
  border-left-color: var(--ink-4); background: var(--anodised); color: var(--ink-3);
}
.pt-pill.pt-status-proposed { background: var(--annunciator-bg); color: var(--annunciator); border-color: var(--annunciator); }
.pt-pill.pt-status-correction-requested { background: var(--pt-claimable-bg); color: var(--pt-claimable); border-color: var(--pt-claimable); }

/* Ticket states: whose move, in colour and always in words. */
.ag-cell.pt-status-waiting-on-you {
  border-left-color: var(--annunciator); background: var(--annunciator-bg); color: var(--annunciator);
}
.ag-cell.pt-status-waiting-on-parts,
.ag-cell.pt-status-scheduled {
  border-left-color: var(--pt-claimable); background: var(--pt-claimable-bg); color: var(--pt-claimable);
}
.ag-cell.pt-status-open {
  border-left-color: var(--ink-4); background: var(--anodised); color: var(--ink-2);
}
.ag-cell.pt-status-resolved {
  border-left-color: var(--led-good); background: var(--led-good-bg); color: var(--led-good);
}

/* One change order awaiting a decision: its own block, because a decision needs a name,
   a reason and a confirmation — which does not fit in a table row. */
.pt-decide { border-top: 1px solid var(--edge-soft); padding: 12px 0; }
.pt-decide:first-of-type { border-top: 0; }

/* The renewal quote, embedded so the decision is made against the document — the WHOLE
   document: A4 proportions (plus viewer chrome), capped to a readable width instead of
   stretching page-wide and showing a strip. */
.pt-quote-embed {
  display: block; width: min(100%, 780px);
  aspect-ratio: 210 / 322;             /* A4 + the viewer's toolbar */
  border: 1px solid var(--edge); border-radius: 4px;
}

/* Renewal states reuse the status scale: quoted asks for a decision, requested is
   waiting on us, approved settles, declined and not-requested stay quiet. */
.ag-cell.pt-status-quoted {
  border-left-color: var(--pt-claimable); background: var(--pt-claimable-bg); color: var(--pt-claimable);
}
.ag-cell.pt-status-requested {
  border-left-color: var(--annunciator); background: var(--annunciator-bg); color: var(--annunciator);
}
.ag-cell.pt-status-not-requested {
  border-left-color: var(--ink-4); background: var(--anodised); color: var(--ink-3);
}
.pt-pill.pt-status-quoted    { background: var(--pt-claimable-bg); color: var(--pt-claimable); border-color: var(--pt-claimable); }
.pt-pill.pt-status-requested { background: var(--annunciator-bg); color: var(--annunciator); border-color: var(--annunciator); }
.pt-pill.pt-status-approved  { background: var(--led-good-bg); color: var(--led-good); border-color: var(--led-good); }
details.pt-month { border-bottom: 1px solid var(--edge-soft); }
details.pt-month > summary {
  display: flex; gap: .75rem; align-items: baseline; padding: 8px 4px;
  cursor: pointer; list-style: none;
}
details.pt-month > summary::-webkit-details-marker { display: none; }
details.pt-month > summary::before { content: "▸"; color: var(--ink-4); font-size: 11px; }
details.pt-month[open] > summary::before { content: "▾"; }
.pt-month-lines { margin: 2px 0 12px 24px; }

/* The overview's cash-out chart — bars stacked by confidence tier. Every column with
   money in it is a link to that week, already unfolded; the transparent hit rect makes
   the whole column clickable, so a 2px bar is still a comfortable target. */
.pt-cashout-scroll { overflow-x: auto; }
.pt-cashout { max-width: 100%; height: auto; }
.pt-cashout a.pt-cash-col { cursor: pointer; }
.pt-cashout rect.pt-cash-hit { fill: transparent; }
.pt-cashout a.pt-cash-col:hover rect.pt-cash-hit { fill: rgba(var(--pt-heat-rgb), .10); }
.pt-cashout a.pt-cash-col:hover text.pt-cash-amt { text-decoration: underline; }
.pt-cashout a.pt-cash-col:focus-visible rect.pt-cash-hit {
  fill: rgba(var(--pt-heat-rgb), .14); outline: 2px solid var(--pt-claimable);
}
.pt-cashout rect.tier-committed { fill: var(--pt-claimable); }
.pt-cashout rect.tier-ready     { fill: var(--pt-claimable); opacity: .6; }
.pt-cashout rect.tier-trend     { fill: var(--annunciator); opacity: .75; }
.pt-cashout rect.tier-contract  { fill: var(--ink-4); opacity: .6; }
.pt-cashout text { fill: var(--ink-3); font-size: 10px; }
.pt-cashout text.pt-cash-amt { fill: var(--ink-2); font-weight: 600; }

/* Resource planner — teams down, weeks across. The heat is the point: --heat is the
   cell's share of the busiest cell (rows) or busiest week (total row), and the alpha
   scales with it, so peak moments read before any number is read. The number is still
   there: colour is never the only signal. */
:root            { --pt-heat-rgb: 47, 111, 208; }
[data-theme="dark"] { --pt-heat-rgb: 122, 167, 232; }

/* border-collapse: collapse silently disables position: sticky on cells in Chrome —
   separate + zero spacing gives the same look and a first column that actually sticks. */
.pt-planner { border-collapse: separate; border-spacing: 0; font-variant-numeric: tabular-nums;
  /* the shell styles tables overflow:hidden (rounded corners); an ancestor with hidden
     overflow between cell and scrollport disables position:sticky — undo it here */
  overflow: visible; }
.pt-planner th.right, .pt-planner td.right { text-align: right; }
.pt-planner td, .pt-planner th { padding: 3px 7px; white-space: nowrap; }
.pt-planner th { font-weight: 550; }
.pt-planner .pt-stick {
  position: sticky; left: 0; z-index: 1; text-align: left;
  background: var(--tblr-card-bg, var(--tblr-bg-surface, #fff));
  border-right: 1px solid var(--edge);
}
.pt-planner .pt-heat { background: rgba(var(--pt-heat-rgb), calc(var(--heat, 0) * 0.45)); }
.pt-planner .pt-zero { color: var(--ink-4); }
.pt-planner .pt-week-now { box-shadow: inset 0 0 0 1px var(--pt-claimable); }
.pt-planner .pt-total td { border-top: 2px solid var(--edge); }
.pt-planner .pt-ms-row td { border-bottom: 1px solid var(--edge); }
.pt-ms { color: var(--annunciator); font-style: normal; position: relative; cursor: default; }
/* The description appears the INSTANT the pointer arrives — no native-title delay,
   no help cursor. Below the marker, so the planner's scroll container cannot clip it. */
.pt-ms:hover::after {
  content: attr(data-ms);
  position: absolute; top: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  z-index: 40; white-space: nowrap;
  background: var(--tblr-bg-surface, #fff); color: var(--ink-2);
  border: 1px solid var(--edge-soft); border-radius: 6px;
  padding: 5px 10px; font-size: 12px; font-weight: 500;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .14);
}

/* Chart-legend dots — the swatch IS the bar colour, so the legend never has to name it. */
.pt-legend-dot {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  margin-right: 5px; vertical-align: 0;
}
.pt-legend-committed { background: var(--pt-claimable); }
.pt-legend-trend     { background: var(--annunciator); opacity: .75; }
.pt-legend-contract  { background: var(--ink-4); opacity: .6; }

/* The brand block hung 12px left of everything below it (section titles carry their own
   12px indent). One indent on the brand and the sidebar reads as a single column. */
@media (min-width: 992px) {
  .navbar-vertical .navbar-brand { padding-left: 12px; }
}

/* Sign-in page: one centred card, no chrome. */
.pt-login-body { background: var(--tblr-bg-surface-secondary, #f5f6f8); }
.pt-login {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.pt-login .card { width: 380px; max-width: 100%; }
.pt-signout button.rb-chip { cursor: pointer; }

/* The editor's borrowed lens ("View as · client/yard") is visibly ON — an editor who
   forgets the lens would misread every page they check. */
.rb-chip-active {
  border-color: var(--tblr-primary, #f59f00);
  background: color-mix(in srgb, var(--tblr-primary, #f59f00) 12%, transparent);
}

/* The top-bar dropdowns (owner, 25-08: "twee dropdown lijstjes"): a <select> wearing
   the chip's clothes. Native menu, custom chevron; submits on change. */
select.rb-chip-select {
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
  padding-right: 28px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6l6 -6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 12px;
}

/* Wrapped grid text reads as prose, not as lines forty pixels apart: normal leading,
   a little breathing room, top-aligned against its row. */
.grid-host .ag-cell.rb-wrap {
  line-height: 1.45;
  padding-top: 9px; padding-bottom: 9px;
  align-items: flex-start;
  word-break: normal; overflow-wrap: anywhere;
}

/* Open invoices on the overview — findable, not loud: plain rows, generous hit areas. */
.pt-open-inv { width: 100%; }
.pt-open-inv td { padding: 5px 14px 5px 0; vertical-align: baseline; white-space: nowrap; }
.pt-open-inv td.right { text-align: right; padding-right: 0; width: 100%; }

/* A pending change order is ONE link: row hover shows it, and the text inside never
   gets selected by an impatient double-click. */
a.pt-decide-link { display: block; color: inherit; text-decoration: none; }
a.pt-decide-link:hover { background: var(--anodised); }

/* Overdue — the one place red is earned: the payment term is a commitment and it has
   been passed. Always with the word and the day count; never a banner. */
.pt-overdue { color: var(--estop, #c0392b); font-weight: 600; }
.grid-host .ag-cell.pt-cell-overdue { color: var(--estop, #c0392b); }

/* The overdue column and its legend dot — the earned red, once. */
.pt-cashout rect.tier-overdue { fill: var(--estop, #c0392b); opacity: .78; }
.pt-cashout text.pt-cash-overdue { fill: var(--estop, #c0392b); }
.pt-legend-overdue { background: var(--estop, #c0392b); opacity: .78; }

/* Prose stays out of cells (dashboard theory W6): one line, ellipsis; the full story
   lives one click deeper on the row's own page. */
.grid-host .ag-cell.rb-clip { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 40px; }

/* The two-column decision block: both columns stretch to one height and each form pins its
   action button to the bottom, so the two buttons land on ONE baseline instead of the left
   one floating high while the right column sags (owner-reported, 2026-08-24: "dit zit niet
   recht uitgelijnd"). Scoped to the row class — other pt-decision forms stay as they are. */
.pt-decision-row > [class*="col-"] { display: flex; flex-direction: column; min-width: 0; }
.pt-decision-row .pt-decision { display: flex; flex-direction: column; flex: 1; min-width: 0; }
/* Belt and braces (owner screenshot: the correction column poked past the card edge in
   one environment we could not reproduce at any width 1024–3400): nothing inside the
   decision columns may ever exceed its column, and the card clips as a last resort. */
.pt-decision-row .form-control,
.pt-decision-row .btn { max-width: 100%; }
.pt-decision-row { overflow-x: clip; }
.pt-decision-row .pt-decision > button,
.pt-decision-row .pt-decision > .btn { margin-top: auto; align-self: flex-start; }

/* Budget readings, decision-aware: a QUOTED difference asks for the client's decision
   (blue = your move, amber = above the baseline and still yours to decline); the
   verdict colours (green windfall / amber overrun) appear only once decided. */
.ag-cell.pt-status-quoted-under-budget, .pt-pill.pt-status-quoted-under-budget {
  border-left-color: var(--pt-claimable); background: var(--pt-claimable-bg); color: var(--pt-claimable);
}
.ag-cell.pt-status-quoted-over-budget, .pt-pill.pt-status-quoted-over-budget {
  border-left-color: var(--annunciator); background: var(--annunciator-bg); color: var(--annunciator);
}
.ag-cell.pt-status-quoted-on-budget,
.ag-cell.pt-status-on-budget { border-left-color: var(--led-good); background: var(--led-good-bg); color: var(--led-good); }

/* Ticket priority: the word carries the value; colour only where attention is due. */
.ag-cell.pt-status-critical, .pt-pill.pt-status-critical {
  border-left-color: var(--annunciator); background: var(--annunciator-bg); color: var(--annunciator);
  font-weight: 650;
}
.ag-cell.pt-status-normal { border-left-color: transparent; background: none; color: var(--ink-3); }

/* A cell that goes somewhere else than its row says so in link ink. */
.grid-host .ag-cell.pt-cell-link { color: var(--pt-claimable); text-decoration: underline; text-decoration-style: dotted; }

/* The two doctrine tabs: the active chip is filled, the inactive one is a chip. */
.pt-tab { cursor: pointer; }
.pt-tab-active { background: var(--chart-ink, #1a1a2e); color: var(--canvas, #fff); border-color: var(--chart-ink, #1a1a2e); }
