/* Engagement lifecycle pages only (pages/engagement.html, engagement-confirm.html, payment.html,
   invoice.html; 19 Sep 2026, M5a / M5b reference). Kept out of components.css like board.css and
   plans.css. Everything scopes to main.eng (hub and confirm), main.pay (payment) or main.inv (invoice).
   Tokens, .card, .pill, .kv, .facts, .notice, .steps, .steps-path, .step__num, .table and .btn come
   from styles.css and components.css.

   STATES BY QUERY (app.js block 34): the page's <main> carries data-stage / data-state / data-flow /
   data-type / data-rating / data-sst in its markup, so the default state renders with no script; the
   script only overwrites them from the query string. Each view block carries the matching
   data-*-view attribute and the rules below show the one that matches. Sean renders one state per
   route and deletes every data-*-view block but the live one. */

/* ---------- Query-driven views (six stages, four states, four flows, two document types, three rating states) ---------- */
[data-stage-view], [data-state-view], [data-flow-view], [data-type-view], [data-rating-view], [data-sst-view] { display: none; }
main[data-stage="confirmed"] [data-stage-view="confirmed"],
main[data-stage="delivery"]  [data-stage-view="delivery"],
main[data-stage="invoiced"]  [data-stage-view="invoiced"],
main[data-stage="claim"]     [data-stage-view="claim"],
main[data-stage="closed"]    [data-stage-view="closed"],
main[data-stage="cancelled"] [data-stage-view="cancelled"],
main[data-stage="postponed"] [data-stage-view="postponed"],
main[data-state="review"]       [data-state-view="review"],
main[data-state="acknowledged"] [data-state-view="acknowledged"],
main[data-state="blocked"]  [data-state-view="blocked"],
main[data-state="due"]      [data-state-view="due"],
main[data-state="paid"]     [data-state-view="paid"],
main[data-state="received"] [data-state-view="received"],
main[data-flow="roster"]   [data-flow-view="roster"],
main[data-flow="own-tp"]   [data-flow-view="own-tp"],
main[data-flow="tp-buyer"] [data-flow-view="tp-buyer"],
main[data-flow="direct"]   [data-flow-view="direct"],
main[data-flow="invited-tp"] [data-flow-view="invited-tp"],
main[data-type="invoice"] [data-type-view="invoice"],
main[data-type="receipt"] [data-type-view="receipt"],
main[data-rating="pending"] [data-rating-view="pending"],
main[data-rating="sent"]    [data-rating-view="sent"],
main[data-rating="both"]    [data-rating-view="both"],
main[data-sst="0"] [data-sst-view="0"],
main[data-sst="1"] [data-sst-view="1"] { display: block; }
/* Table rows and inline spans keep their own display when shown. */
main[data-sst="1"] tr[data-sst-view="1"], main[data-sst="0"] tr[data-sst-view="0"] { display: table-row; }
main[data-state="due"] span[data-state-view="due"], main[data-state="paid"] span[data-state-view="paid"],
main[data-state="blocked"] span[data-state-view="blocked"], main[data-state="received"] span[data-state-view="received"] { display: inline; }
main[data-flow="roster"] a[data-flow-view="roster"], main[data-flow="own-tp"] a[data-flow-view="own-tp"], main[data-flow="tp-buyer"] a[data-flow-view="tp-buyer"], main[data-flow="direct"] a[data-flow-view="direct"], main[data-flow="invited-tp"] a[data-flow-view="invited-tp"],
main[data-type="invoice"] a[data-type-view="invoice"], main[data-type="receipt"] a[data-type-view="receipt"] { display: inline-block; }

/* ---------- Third viewer: the roster training provider (?as=tp, app.js 34 adds as-company for the header and as-tp for main) ---------- */
/* [data-tp] show/hide moved to components.css 4 Oct 2026 (brief-new.html needs it too). */
.as-tp main [data-company], .as-tp main [data-trainer] { display: none !important; }
.as-tp main [data-trainer][data-tp-keep] { display: block !important; } /* confirm page: the read-only terms card shows to the roster provider too (4 Oct) */

/* ---------- Hub head ---------- */
.eng { padding-bottom: 6rem; }
.eng-head        { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem 1.5rem; flex-wrap: wrap; margin-top: 1rem; }
.eng-head h1     { margin: 0; }  /* size from the h1 row (CP 19 Sep: one title register) */
.eng-head__meta  { margin-top: 0.375rem; font-size: var(--text-body-sm); color: var(--muted-foreground); }
.eng-head__meta .mono { color: var(--foreground); }
.eng-head .pill  { flex-shrink: 0; margin-top: 0.375rem; }

/* Stage strip: the sheet's stepper (components.css .steps), labels under the dots so five stages fit a phone. */
.eng-steps       { display: flex; align-items: flex-start; margin-top: 1.5rem; padding: 1.25rem 1.25rem 1rem; background: var(--card); border-radius: var(--radius-lg); box-shadow: var(--shadow-card); }
.eng-step        { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; min-width: 0; font-size: var(--text-body-sm); font-weight: 600; color: var(--muted-foreground); text-align: center; }
.eng-step .steps__dot { width: 1.75rem; height: 1.75rem; font-size: var(--text-caption); }
.eng-step .steps__dot svg { width: 0.875rem; height: 0.875rem; }
.eng-step.is-active, .eng-step.is-done { color: var(--foreground); }
.eng-steps .steps__line { margin-top: 0.875rem; min-width: 0.75rem; }
.eng-steps--cancelled .steps__line, .eng-steps--cancelled .eng-step:not(.is-done) { opacity: 0.5; }
@media (max-width: 639px) {
  .eng-steps     { padding: 1rem 0.75rem 0.75rem; }
  .eng-step      { font-size: var(--text-overline); gap: 0.375rem; }
  .eng-step .steps__dot { width: 1.5rem; height: 1.5rem; }
  .eng-steps .steps__line { margin-top: 0.75rem; min-width: 0.375rem; margin-inline: 0; }
}

/* ---------- Two columns: timeline left, booking record right (thread-layout widths) ---------- */
.eng-layout      { display: grid; gap: 1.5rem; grid-template-columns: 1fr; margin-top: 1.5rem; }
@media (min-width: 1024px) { .eng-layout { grid-template-columns: minmax(0, 1fr) 20rem; align-items: start; } }
.eng-layout > * { min-width: 0; }

/* Timeline: the home's .steps-path pattern (dot on the left, hairline between) written out here because
   components.css turns .steps-path into a four-column grid at desktop width. One card per stage.
   The dot reads the stage state: done (navy, tick), current (navy ring), next (grey outline). */
.eng-timeline    { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: 1fr; gap: 1rem; }
.eng-timeline li { position: relative; padding-left: 3rem; }
.eng-timeline li::after { content: ""; position: absolute; left: calc(1rem - 1px); top: 2.25rem; bottom: -1rem; width: 2px; border-radius: 2px; background: var(--grey-200); }
.eng-timeline li:last-child::after { display: none; }
.eng-timeline .step__num { position: absolute; left: 0; top: 0.125rem; }
.eng-timeline .step__num { width: 2rem; height: 2rem; }
.eng-timeline .step__num svg { width: 1rem; height: 1rem; }
.eng-timeline li[data-state="current"] .step__num { background: var(--card); color: var(--primary); border: 2px solid var(--primary); box-shadow: 0 0 0 4px rgba(13, 59, 102, 0.10); }
.eng-timeline li[data-state="next"] .step__num { background: var(--card); color: var(--muted-foreground); border: 2px solid var(--grey-200); }
.eng-timeline li[data-state="cancelled"] .step__num { background: var(--card); color: var(--destructive); border: 2px solid color-mix(in oklab, var(--destructive) 40%, var(--border)); }

.stage-card      { padding: 1.125rem 1.25rem; }
.stage-card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.75rem 1rem; flex-wrap: wrap; }
.stage-card__title { margin: 0; font-family: var(--font-sans); font-size: var(--text-subheading); font-weight: 600; letter-spacing: 0; }
.stage-card__head .pill { flex-shrink: 0; }
.stage-card__line { margin-top: 0.375rem; font-size: var(--text-body-sm); color: var(--muted-foreground); }
.stage-card__line strong { color: var(--foreground); font-weight: 500; }
.stage-card .kv  { margin-top: 0.875rem; font-size: var(--text-body-sm); gap: 0.375rem 1.25rem; }
.stage-card .form-actions { margin-top: 1rem; }
.stage-card__sep { margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid var(--grey-100); }
li[data-state="next"] .stage-card { box-shadow: none; border: 1px dashed var(--grey-300); background: transparent; }
li[data-state="next"] .stage-card__title { color: var(--muted-foreground); }
li[data-state="done"] .stage-card { padding-block: 0.875rem; }
/* Money rows inside a stage card and on the payment page: label left, tabular amount right, hairline between. */
.money           { list-style: none; margin: 0.875rem 0 0; padding: 0; font-size: var(--text-body-sm); }
.money li        { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; padding: 0.5rem 0; border-top: 1px solid var(--grey-100); }
.money li:first-child { border-top: 0; padding-top: 0; }
.money li span:last-child { font-family: var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.money li.money--total { border-top: 1px solid var(--grey-200); margin-top: 0.25rem; padding-top: 0.75rem; font-weight: 600; }
.money li.money--total span:last-child { font-size: var(--text-subheading); color: var(--primary); }
.money li small  { display: block; font-size: var(--text-caption); color: var(--muted-foreground); font-weight: 400; }

/* Rating form (Upwork end-of-contract pattern): one question per axis, five stars as radios, one comment. */
.rate            { border: 0; padding: 0; margin: 1rem 0 0; }
.rate legend     { font-size: var(--text-body-sm); font-weight: 600; color: var(--grey-700); padding: 0; }
.rate__stars     { display: inline-flex; flex-direction: row-reverse; gap: 0.125rem; margin-top: 0.375rem; }
.rate__stars input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.rate__stars label { cursor: pointer; color: var(--grey-300); padding: 0.125rem; border-radius: var(--radius-xs); transition: color .12s, transform .16s var(--ease-out); }
.rate__stars label svg { width: 1.5rem; height: 1.5rem; display: block; }
.rate__stars input:checked ~ label, .rate__stars label:hover, .rate__stars label:hover ~ label { color: var(--warning); }
.rate__stars label:active { transform: scale(0.92); }
.rate__stars input:focus-visible + label { outline: 2px solid var(--ring); outline-offset: 2px; }
.rate__stars label span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
/* Read-only stars for a rating already in. */
.rate-shown      { display: inline-flex; gap: 0.125rem; color: var(--grey-300); vertical-align: middle; }
.rate-shown svg  { width: 1rem; height: 1rem; }
.rate-shown svg.is-on { color: var(--warning); }

/* Rail: the booking record. */
.eng-rail .side-card + .side-card { margin-top: 1.25rem; }
.eng-rail .facts { margin-top: 0.5rem; }
.eng-rail .kv    { margin-top: 0.75rem; font-size: var(--text-body-sm); gap: 0.375rem 1rem; }
.eng-rail .checklist { gap: 0.375rem; }
.eng-rail .checklist a { color: var(--primary); }
/* Supplier details fold on the company view (19 Sep, OPEN-DECISIONS #48): a native details under the booking record. */
.eng-rail .supplier-fold summary { margin-top: 0.75rem; font-size: var(--text-body-sm); }
.eng-rail .supplier-fold > :not(summary) { margin-top: 0.25rem; }

/* ---------- Confirm page (Cl. 6.9 order summary) ---------- */
.confirm-summary { max-width: var(--w-3xl); }
.confirm-summary .card + .card { margin-top: 1.25rem; }
.confirm-summary .kv { font-size: var(--text-body); gap: 0.5rem 1.5rem; margin-top: 1rem; }
.confirm-summary .money { font-size: var(--text-body); }
.confirm-side    { display: grid; gap: 0.75rem; margin-top: 1rem; }
.confirm-side__row { display: flex; align-items: center; gap: 0.75rem; font-size: var(--text-body-sm); }
.confirm-side__row .tick { flex-shrink: 0; }

/* ---------- Payment page ---------- */
.pay             { padding-bottom: 6rem; }
.pay-layout      { display: grid; gap: 1.5rem; grid-template-columns: 1fr; margin-top: 1.5rem; }
@media (min-width: 1024px) { .pay-layout { grid-template-columns: minmax(0, 1fr) 20rem; align-items: start; } }
.pay-layout > * { min-width: 0; }
.pay-card        { padding: 1.5rem; }
.pay-card h2     { margin: 0; font-size: var(--text-subheading); font-family: var(--font-sans); letter-spacing: 0; }
.pay-card .kv    { margin-top: 1rem; font-size: var(--text-body-sm); gap: 0.375rem 1.25rem; }
.pay-card .money { margin-top: 1.25rem; font-size: var(--text-body); }
.pay-card .notice { margin-top: 1.25rem; }
.pay-due         { margin-top: 1rem; font-size: var(--text-body-sm); color: var(--muted-foreground); }
.pay-due strong  { color: var(--foreground); font-weight: 500; }

/* ---------- Invoice and receipt: a printable document ---------- */
.inv             { padding-bottom: 6rem; }
.inv-doc         { max-width: var(--w-3xl); margin-top: 1.5rem; padding: 2.5rem; background: var(--card); border-radius: var(--radius-card); box-shadow: var(--shadow-card); font-size: var(--text-body); }
.inv-doc__head   { display: flex; justify-content: space-between; align-items: flex-start; gap: 1.5rem; flex-wrap: wrap; }
.inv-doc__head h1 { margin: 0; }  /* size from the h1 row (CP 19 Sep) */
.inv-doc__logo   { height: 1.5rem; width: auto; display: block; }
.inv-doc__ref    { text-align: right; font-size: var(--text-body-sm); color: var(--muted-foreground); }
.inv-doc__ref .mono { display: block; color: var(--foreground); font-weight: 600; font-size: var(--text-body); }
.inv-doc__parties { display: grid; gap: 1.5rem; grid-template-columns: 1fr; margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--grey-200); }
@media (min-width: 640px) { .inv-doc__parties { grid-template-columns: 1fr 1fr; } }
.inv-doc__party  { font-size: var(--text-body-sm); line-height: 1.55; }
.inv-doc__party .medium { display: block; font-size: var(--text-body); }
.inv-doc__label  { display: block; margin-bottom: 0.25rem; font-size: var(--text-overline); font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase; color: var(--muted-foreground); } /* the sheet's th register; CP 15 Sep: uppercase th on */
.inv-doc .table-wrap { margin-top: 2rem; border-color: var(--grey-200); }
.inv-doc .table td { vertical-align: top; }
.inv-doc .table td small { display: block; margin-top: 0.25rem; color: var(--muted-foreground); font-size: var(--text-caption); }
.inv-doc .money  { margin-top: 0; padding: 0.25rem 1.125rem 0.75rem; }
.inv-doc__note   { margin-top: 1.5rem; font-size: var(--text-body-sm); color: var(--muted-foreground); line-height: 1.55; }
.inv-doc__note + .inv-doc__note { margin-top: 0.5rem; }
.inv-doc__foot   { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--grey-200); font-size: var(--text-caption); color: var(--muted-foreground); line-height: 1.55; }
.inv-actions     { display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center; margin-top: 1rem; }
@media (max-width: 639px) { .inv-doc { padding: 1.5rem 1.25rem; } .inv-doc__ref { text-align: left; } }
@media print {
  .site-header, .site-footer, .inv-actions, .back-link, .states-toggle, .cookie-banner { display: none !important; }
  body, .page { background: #fff; }
  .inv { padding: 0; }
  .inv-doc { max-width: none; margin: 0; padding: 0; box-shadow: none; border-radius: 0; }
  .inv-doc .table-wrap { border-color: #cbd5e1; }
  a { color: inherit; text-decoration: none; }
}

/* ════ Cancel after confirmation, and the invited training provider's wait (22 Sep 2026, M5a) ════
   Everything between this banner and the next one belongs to that task. Three small state axes, all on
   <main>, all written by app.js block 34 from the query string, all defaulting in the markup so the page
   renders with no script:

     [data-cancel]     the cancel dialog's money line. "hrdc" is the default (the confirmed record is
                       payflow 1, an HRD Corp flow, where nothing moves); ?flow=direct swaps in the
                       refund line. BUILD-PLAN 5.8.
     [data-cancelled]  which cancelled card ?stage=cancelled shows. "direct" is the default and is the
                       19 September card, untouched; ?flow=roster swaps in the HRD Corp one.
     [data-tp-view]    the invited provider's wait on the Confirmed stage and on thread.html, keyed to
                       main[data-tp-state] = none (default) | invited-pending | invited-verified |
                       invited-fallback. AT-37 (b) and (c). The attribute is data-tp-STATE, not data-tp:
                       [data-tp] above is the third-viewer marker and hides whatever carries it.

   Sean: one state per route from the record; delete the blocks that do not apply. */
.eng [data-cancel="direct"] { display: none; }
.eng[data-flow="direct"] [data-cancel="direct"] { display: block; }
.eng[data-flow="direct"] [data-cancel="hrdc"]   { display: none; }
.eng [data-cancelled="roster"] { display: none; }
.eng[data-flow="roster"] [data-cancelled="roster"] { display: block; }
.eng[data-flow="roster"] [data-cancelled="direct"] { display: none; }

/* li[data-tp-view] is spelled out because .checklist li already sets display: flex at the same weight. */
[data-tp-view], li[data-tp-view] { display: none; }
main[data-tp-state="none"]             [data-tp-view="none"],
main[data-tp-state="invited-pending"]  [data-tp-view="invited-pending"],
main[data-tp-state="invited-verified"] [data-tp-view="invited-verified"],
main[data-tp-state="invited-fallback"] [data-tp-view="invited-fallback"] { display: block; }
/* Pills and checklist rows keep their own display when shown. */
main[data-tp-state="none"]             .pill[data-tp-view="none"],
main[data-tp-state="invited-pending"]  .pill[data-tp-view="invited-pending"],
main[data-tp-state="invited-verified"] .pill[data-tp-view="invited-verified"],
main[data-tp-state="invited-fallback"] .pill[data-tp-view="invited-fallback"] { display: inline-flex; }
main[data-tp-state="invited-pending"]  li[data-tp-view="invited-pending"],
main[data-tp-state="invited-fallback"] li[data-tp-view="invited-fallback"],
main[data-tp-state="invited-pending"]  .tp-wait[data-tp-view="invited-pending"],
main[data-tp-state="invited-verified"] .tp-wait[data-tp-view="invited-verified"],
main[data-tp-state="invited-fallback"] .tp-wait[data-tp-view="invited-fallback"] { display: flex; }
/* ?tp=replacement-needed (6 Oct 2026, OPEN-DECISIONS #66 (4)): the invited provider was released; engagement.html only. */
main[data-tp-state="replacement-needed"] [data-tp-view="replacement-needed"] { display: block; }
main[data-tp-state="replacement-needed"] .pill[data-tp-view="replacement-needed"] { display: inline-flex; }
main[data-tp-state="replacement-needed"] li[data-tp-view="replacement-needed"] { display: flex; }
/* 7 Oct 2026 (second cold read, A22): components.css shows [data-company] with display: revert !important under .as-company,
   which outranked the [data-tp-view] hide above, so "Confirm booking" (data-tp-view="none" data-company) rendered on
   thread.html?tp=invited-verified&as=company. A [data-tp-view] element whose view is not the active one stays hidden whatever
   role attribute it also carries. "invited" is thread.html's any-invited-state value and is left to its own rule below. */
main[data-tp-state="none"]               [data-tp-view]:not([data-tp-view="none"]),
main[data-tp-state="invited-pending"]    [data-tp-view]:not([data-tp-view="invited-pending"]):not([data-tp-view="invited"]),
main[data-tp-state="invited-verified"]   [data-tp-view]:not([data-tp-view="invited-verified"]):not([data-tp-view="invited"]),
main[data-tp-state="invited-fallback"]   [data-tp-view]:not([data-tp-view="invited-fallback"]):not([data-tp-view="invited"]),
main[data-tp-state="replacement-needed"] [data-tp-view]:not([data-tp-view="replacement-needed"]) { display: none !important; }

/* The dialog's money line: the one figure the canceller needs, in the body register, not the muted one
   .dialog p sets. No border, no tint: it is a statement of fact, not an alert. */
.cancel-line     { margin-top: 1rem; font-size: var(--text-body-sm); color: var(--foreground); line-height: 1.5; }
.cancel-line .mono { font-weight: 600; }
.dialog .cancel-line + .caption { margin-top: 0.375rem; }

/* The wait strip on thread.html: a hairline above the composer, the composer's own padding and the
   caption register, so it reads as part of the conversation frame rather than a banner. */
/* .tp-wait gets its display from the show rules above, so it stays hidden until its state is on. */
.tp-wait         { align-items: flex-start; gap: 0.625rem; border-top: 1px solid var(--border);
                   padding: 0.875rem 1.25rem 0.125rem; font-size: var(--text-body-sm);
                   color: var(--muted-foreground); line-height: 1.5; }
.tp-wait svg     { flex-shrink: 0; width: 1.125rem; height: 1.125rem; margin-top: 0.125rem; }
.tp-wait strong  { color: var(--foreground); font-weight: 600; }
/* The composer's own hairline would double the strip's, so it goes while a strip is showing. */
main[data-tp-state="invited-pending"]  .thread .composer,
main[data-tp-state="invited-verified"] .thread .composer,
main[data-tp-state="invited-fallback"] .thread .composer { border-top: 0; }
/* thread.html ?tp= shows eng-108's conversation (CorporateLearn Berhad and Ahmad Razif). A provider that is
   itself the buyer never gets a third participant (AT-37 (d)), so the invite views cannot sit on the default
   coaching-skills conversation. data-tp-view="invited" matches all three invited states; scoped to
   main.eng-thread so engagement.html's own span toggles keep their block display. */
main.eng-thread[data-tp-state^="invited-"] [data-tp-view="invited"] { display: block; }
main.eng-thread[data-tp-state="none"]             span[data-tp-view="none"],
main.eng-thread[data-tp-state^="invited-"]        span[data-tp-view="invited"],
main.eng-thread[data-tp-state="invited-pending"]  span[data-tp-view="invited-pending"],
main.eng-thread[data-tp-state="invited-verified"] span[data-tp-view="invited-verified"],
main.eng-thread[data-tp-state="invited-fallback"] span[data-tp-view="invited-fallback"] { display: inline; }
.thread__group { flex-direction: column; gap: 0.875rem; }
main.eng-thread[data-tp-state="none"]      :is(.thread__group, .facts, .row)[data-tp-view="none"],
main.eng-thread[data-tp-state^="invited-"] :is(.thread__group, .facts)[data-tp-view="invited"],
main.eng-thread[data-tp-state="invited-verified"] li[data-tp-view="invited-verified"] { display: flex; }
/* 6 Oct (OPEN-DECISIONS #66(8)): ?history=full|join on thread.html, the scope the trainer chose when the training
   provider joined; app.js 34 writes it on <main> as data-history. Spans only: the join message and the wait strip. */
main.eng-thread [data-history-view] { display: none; }
main.eng-thread[data-history="full"] [data-history-view="full"],
main.eng-thread[data-history="join"] [data-history-view="join"] { display: inline; }
/* Sweep #98 m3 2.25 (15 Oct 2026): the grant documents slots wait until the invited provider is verified; the pending
   provider reads the one line above them instead. */
main.eng-thread[data-tp-state="invited-pending"] [data-grant-docs] { display: none !important; }
/* Sanjay has no photo in the mock: the buyer avatar drops Aiman's image and shows initials. */
main.eng-thread[data-tp-state^="invited-"] .thread__head .avatar[data-trainer] img,
main.eng-thread[data-tp-state^="invited-"] .participants li:first-child img { display: none; }
/* ════ End of the 22 September M5a block ════ */

/* ════ M5b: two-sided remittance, overdue and disputed, reseller records (22 Sep 2026) ════
   Everything between this banner and the next one belongs to that task. No new mechanism: the same
   [data-state-view] and [data-type-view] rules as the top of this file, extended to the eleven new
   payment.html states and the three new invoice.html documents. app.js block 34 already copies any
   ?state= and ?type= value onto <main>, so no script change was needed.

     payment.html   ?state=remitted | confirmed | overdue | disputed          roster, tp-buyer
                    ?state=remitted | confirmed                              own-tp
                    ?state=deposit-invoiced | deposit-received | balance-invoiced | balance-received |
                            payout-due | payout-sent | held                  direct
     invoice.html   ?type=deposit | balance | payout-advice                  the reseller documents

   Sean: one state and one document per route from the record; delete the blocks that do not apply. */
main[data-state="remitted"]         [data-state-view="remitted"],
main[data-state="confirmed"]        [data-state-view="confirmed"],
main[data-state="overdue"]          [data-state-view="overdue"],
main[data-state="disputed"]         [data-state-view="disputed"],
main[data-state="deposit-invoiced"] [data-state-view="deposit-invoiced"],
main[data-state="deposit-received"] [data-state-view="deposit-received"],
main[data-state="balance-invoiced"] [data-state-view="balance-invoiced"],
main[data-state="balance-received"] [data-state-view="balance-received"],
main[data-state="payout-due"]       [data-state-view="payout-due"],
main[data-state="payout-sent"]      [data-state-view="payout-sent"],
main[data-state="held"]             [data-state-view="held"],
main[data-type="deposit"]       [data-type-view="deposit"],
main[data-type="balance"]       [data-type-view="balance"],
main[data-type="payout-advice"] [data-type-view="payout-advice"] { display: block; }
/* The Received row's inline spans, as the four 19 September states already do. */
main[data-state="remitted"]  span[data-state-view="remitted"],
main[data-state="confirmed"] span[data-state-view="confirmed"],
main[data-state="overdue"]   span[data-state-view="overdue"],
main[data-state="disputed"]  span[data-state-view="disputed"] { display: inline; }
/* The back link above each document keeps its own display. */
main[data-type="deposit"]       a[data-type-view="deposit"],
main[data-type="balance"]       a[data-type-view="balance"],
main[data-type="payout-advice"] a[data-type-view="payout-advice"] { display: inline-block; }
/* A second .kv inside a payment card sits under the block above it, not tight against it. */
.pay-card .kv + .kv, .pay-card .pay-due + .kv, .pay-card .notice + .kv { margin-top: 1rem; }
/* The direct flow's shared rows state that the deposit is in, which is true of every state on that flow
   except the one where it has only been invoiced. There they step aside and the deposit-invoiced block
   carries the figures instead. */
main[data-state="deposit-invoiced"] [data-deposit-paid] { display: none !important; }
main.pay:not([data-state="deposit-invoiced"]) [data-deposit-due] { display: none !important; }
/* 8 Oct 2026 (third cold read): the company title on the direct flow follows the state. "Pay the balance" only while a balance is open. */
main.pay[data-flow="direct"]:not([data-state="due"]):not([data-state="balance-invoiced"]):not([data-state="blocked"]) [data-balance-open] { display: none !important; }
main.pay[data-flow="direct"][data-state="due"] [data-balance-settled], main.pay[data-flow="direct"][data-state="balance-invoiced"] [data-balance-settled], main.pay[data-flow="direct"][data-state="blocked"] [data-balance-settled], main.pay[data-flow="direct"][data-state="deposit-invoiced"] [data-balance-settled] { display: none !important; }
/* ════ End of the 22 September M5b block ════ */

/* Selection colour and the caret follow the palette on these pages (the parts nobody draws). */
.eng ::selection, .pay ::selection, .inv ::selection { background: var(--navy-100); color: var(--navy-900); }
.eng textarea, .pay input { caret-color: var(--primary); }

/* ---------- Participant feedback on the In delivery card (23 Sep 2026, M5b, HANDOFF 89) ----------
   The link, the QR and Copy link for the trainer; the coverage line for both sides. The QR is its own class,
   not components.css .qr: that one is print-only (T12), and this one is meant to be shown on a projector or
   handed round on the trainer's device. The image is ruling R6's placeholder (assets/img/qr-mock.svg). */
.eng .fb-card__title { margin: 0; font-family: var(--font-sans); font-size: var(--text-body-sm); font-weight: 600; letter-spacing: 0; color: var(--foreground); }
.eng .fb-card__body  { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1rem 1.25rem; margin-top: 0.75rem; }
.eng .fb-qr          { display: block; flex-shrink: 0; width: 8rem; height: 8rem; padding: 0.375rem; background: #fff;
                       border: 1px solid var(--border); border-radius: var(--radius-sm); }
.eng .fb-card__text  { flex: 1 1 12rem; min-width: 0; }
.eng .fb-card__link  { display: inline-block; font-family: var(--font-mono); font-size: var(--text-body-sm); color: var(--primary);
                       overflow-wrap: anywhere; text-underline-offset: 2px; }
.eng .fb-card .form-actions { margin-top: 0.75rem; }
.eng .fb-card__text .stage-card__line { margin-top: 0.75rem; }
.eng .fb-card [data-copy-link] { min-width: 8.5rem; }  /* "Copy link" and "Link copied" hold one width, so the swap does not jog the row */
.as-tp .eng .fb-card { display: none; }  /* the roster provider views neither the link nor the coverage (no brief for it); eng-102 has no provider anyway */
.eng .fb-card__date { white-space: nowrap; }

/* ---------- HRD Corp grant approval (8 Oct 2026, OPEN-DECISIONS #76) ----------
   ?grant=pending|approved, app.js 34 writes data-grant on <main>. [data-grant-view] blocks show for the matching state. */
[data-grant-view] { display: none; }
main[data-grant="pending"] [data-grant-view="pending"], main[data-grant="approved"] [data-grant-view="approved"] { display: block; }
main[data-grant="pending"] .pill[data-grant-view="pending"] { display: inline-flex; }

/* ---------- Payment ledger "Where it goes" fold (9 Oct 2026, HANDOFF 139) ----------
   Shown while the payment is still to be made; after it the paid notice carries the same two numbers. */
main.pay[data-state="paid"] details.prose-fold, main.pay[data-state="received"] details.prose-fold,
main.pay[data-state="remitted"] details.prose-fold, main.pay[data-state="confirmed"] details.prose-fold { display: none; }

/* ---------- Payment page, state-first layout (9 Oct 2026, HANDOFF 140) ----------
   Title and ledger wording switch per state with inline spans; the four M5b states join the inline rule. */
main[data-state="remitted"] span[data-state-view="remitted"], main[data-state="confirmed"] span[data-state-view="confirmed"],
main[data-state="overdue"] span[data-state-view="overdue"], main[data-state="disputed"] span[data-state-view="disputed"] { display: inline; }
.pay-card > .notice { margin-top: 0; }
main[data-state="deposit-invoiced"] span[data-state-view="deposit-invoiced"], main[data-state="deposit-received"] span[data-state-view="deposit-received"],
main[data-state="balance-invoiced"] span[data-state-view="balance-invoiced"], main[data-state="balance-received"] span[data-state-view="balance-received"],
main[data-state="payout-due"] span[data-state-view="payout-due"], main[data-state="payout-sent"] span[data-state-view="payout-sent"],
main[data-state="held"] span[data-state-view="held"] { display: inline; }
main[data-state="review"] span[data-state-view="review"], main[data-state="acknowledged"] span[data-state-view="acknowledged"] { display: inline; }
