/* Panelman's chrome, and the console's colour contract.

   The console never assumes a theme (package decision 2026-09-03-S). Its
   colours resolve through `--mkadmin-*` FIRST, then a host's own published
   properties, then CSS system colours as a guaranteed-paired last resort. A
   host that wants to state exactly what the console should use sets the
   `--mkadmin-*` block and needs nothing else. Panelman does — see `.mkadmin`
   at the foot of this file.

   THE RULE: NEVER SET A COLOUR ON ONE SIDE ONLY. Every rule that sets a
   background sets its text colour in the same rule, and the reverse. That is
   package decision 2026-09-03-T, and every one of the five faults found on the
   first dark host was that mistake. */

:root {
  /* Declared so the browser paints native control parts, scrollbars and any
     system colour to match this application rather than defaulting to the
     reader's preference. Panelman is a light design and says so.

     NOTE: this does NOT reach the console. `.mkadmin` sets its own
     `color-scheme: light dark` so that ITS last-resort system colours follow
     the reader — which is correct for a package that cannot know its host,
     and is exactly why the console must be told its colours explicitly rather
     than left to fall through. See the `.mkadmin` block. */
  color-scheme: light;

  /* Q19, 6 -- NEUTRAL AND VIBRANT, NO MAKARIOS BRANDING (Johann's ruling,
     2026-09-23). Neutral greys for everything that is not saying something,
     and ONE accent, teal (ruled at Step 0), for what is actionable and for
     where you are in the navigation, and nowhere else. The navy it replaces
     read as a default. The top bar is a neutral dark grey, not the accent:
     a bar painted in the accent is decoration, and decoration gets no
     colour. */
  --pm-bg: #f4f5f6;
  --pm-surface: #ffffff;
  --pm-surface-2: #eef0f2;
  --pm-ink: #1b1f24;
  --pm-ink-soft: #57606a;
  --pm-ink-faint: #6b7280;
  --pm-line: #d9dde2;
  --pm-line-soft: #eceef1;
  --pm-accent: #0f766e;
  --pm-accent-ink: #ffffff;
  --pm-accent-soft: #e3f2f0;
  --pm-bar-bg: #22282f;
  --pm-bar-ink: #e9edf1;
  --pm-bar-ink-soft: #b4bcc5;
  /* An amount going OUT of the bank, beside the category that already says
     so. Colour carrying meaning (6), not decoration. */
  --pm-out-ink: #9f2a1f;
  --pm-danger: #b3261e;
  --pm-ok-bg: #e6f4ea;
  --pm-ok-ink: #14532d;
  --pm-ok-line: #96d5ab;
  --pm-warn-bg: #fdece9;
  --pm-warn-ink: #7a2318;
  --pm-warn-line: #f0b0a4;
  --pm-muted-bg: #eef0f2;

  /* ==================================================================
     Q19, 2 -- DENSITY AND RHYTHM, IN ONE PLACE.
     ==================================================================
     Sixteen slices each tuned their own screen and nobody designed the
     whole. These are the numbers every screen now draws from, and a
     template that sets its own spacing or sizing is refused by
     `step_check.sh`, so the next slice cannot quietly undo this one. */

  /* Type scale. Body is 14px: an accounting screen, read at a desk. */
  --pm-fs-body: 14px;
  --pm-fs-title: 1.3rem;      /* page title, h1 */
  --pm-fs-panel: 1rem;        /* panel heading, h2 */
  --pm-fs-sub: .92rem;        /* h3 inside a panel */
  --pm-fs-table: .875rem;     /* table text */
  --pm-fs-note: .8rem;        /* secondary notes, table headers, labels */

  /* Data tables: tight rows, as an accounting grid. */
  --pm-row-y: .22rem;
  --pm-cell-x: .5rem;

  /* Fields. One input height for text, date and select alike. */
  --pm-input-h: 1.9rem;
  --pm-input-x: .45rem;
  --pm-label-gap: .2rem;      /* label to its field, label above */
  --pm-label-col: 8.5rem;     /* the label column, label beside (4) */

  /* The gaps, smallest to largest. */
  --pm-gap-field: .45rem;     /* between fields */
  --pm-gap-group: .9rem;      /* between groups of fields */
  --pm-gap-panel: .85rem;     /* between panels */
  --pm-pad-panel: .7rem .95rem;

  /* Prose never runs the width of the window (3). Tables and fields still
     use the width; a sentence does not. */
  --pm-measure: 88ch;

  /* Short fields, sized to what they hold (3). */
  --pm-w-date: 10.5rem;
  --pm-w-ref: 8.5rem;
  --pm-w-amount: 9rem;
  --pm-w-code: 8rem;
  --pm-w-name: 16rem;
}

html { font-size: 16px; }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--pm-bg);
  color: var(--pm-ink);
  font: var(--pm-fs-body)/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

a { color: var(--pm-accent); }

/* Q8 ITEM 4. Sticky, not fixed: `position: fixed` takes the bar out of flow
   and every page then needs a matching top padding that nothing enforces --
   the first screen somebody adds without it loses its heading under the bar.
   Sticky keeps the bar in flow, so the layout needs no compensation at all,
   and the console's own screens inherit it because they render inside this
   chrome. The z-index is above the cards and above `<details>` content, which
   is the only thing on these pages that grows upward into it. */
.topbar {
  position: sticky; top: 0; z-index: 20;
  background: var(--pm-bar-bg); color: var(--pm-bar-ink);
}

/* Q12 ITEM 6 — AN ANCHOR MUST NOT LAND UNDER THE BAR (ruled 2026-09-18).

   Johann, on the invoice panel: the anchor scrolls it to the top of the window,
   where the bar covers the first rows. That is what a sticky bar does to every
   `#fragment` on every page -- the browser puts the target at the top of the
   viewport and the bar is sitting there. `scroll-margin-top` is the one-property
   answer and it needs no JavaScript.

   IT IS NOT ONLY THE INVOICE PANEL. The same defect was on every anchor this
   application has: the exceptions pager's `#jobs_no_invoice`, the "capture the
   quote details" link's `#quote-details`, and the panel's own Close link back to
   `#unlinked`. One rule, every anchor target, so the next one added inherits it
   instead of rediscovering this.

   THE NUMBER. The bar is one line of 15px text in .7rem of padding -- about
   45px -- and it WRAPS on a narrow screen, so a value that exactly cleared one
   line would be short on two. 5rem clears one line with room and most of two,
   and measuring the bar to the pixel would need the JavaScript this application
   does not have. Stated rather than tuned. */
:root { --pm-anchor-clearance: 6rem; }  /* Q19: the bar is two rows now. */
.card[id], .invoice-panel[id], [id="quote-details"], [id="invoice"], [id="unlinked"],
[id^="unlinked-"] {
  scroll-margin-top: var(--pm-anchor-clearance);
}
/* Q19, 5 -- THE TWO-LEVEL BAR. The section bar is the dark row; the screen
   bar under it is the section's screens on the page's own surface. The
   current section and the current screen are the accent's second and last
   job: a bar under the section, a tint and a bar under the screen. */
.sectionbar {
  display: flex; align-items: stretch; gap: 1.5rem; flex-wrap: wrap;
  padding: 0 1.25rem; min-height: 2.6rem;
}
.topbar a { color: var(--pm-bar-ink); text-decoration: none; }
.topbar a:hover { text-decoration: underline; }
.brand { font-weight: 700; letter-spacing: .02em; display: flex; align-items: center; }
.topbar nav.sections { display: flex; gap: .25rem; }
.topbar nav.sections a {
  display: flex; align-items: center; padding: 0 .75rem;
  color: var(--pm-bar-ink-soft); border-bottom: 3px solid transparent;
  border-top: 3px solid transparent;
}
.topbar nav.sections a:hover { color: var(--pm-bar-ink); text-decoration: none; }
.topbar nav.sections a.is-current {
  color: var(--pm-bar-ink); font-weight: 600; border-bottom-color: var(--pm-accent);
}
.screenbar {
  display: flex; flex-wrap: wrap; gap: .15rem; padding: 0 1rem;
  background: var(--pm-surface); color: var(--pm-ink);
  border-bottom: 1px solid var(--pm-line);
}
.topbar .screenbar a {
  color: var(--pm-ink-soft); padding: .45rem .7rem; font-size: .9rem;
  border-bottom: 2px solid transparent;
}
.topbar .screenbar a:hover { color: var(--pm-ink); text-decoration: none; background: var(--pm-surface-2); }
.topbar .screenbar a.is-current {
  color: var(--pm-accent); font-weight: 600; border-bottom-color: var(--pm-accent);
  background: var(--pm-accent-soft);
}
.who { margin-left: auto; display: flex; align-items: center; gap: 1rem; font-size: .9rem; color: var(--pm-bar-ink-soft); }
.inline { display: inline; }
.linkish {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--pm-bar-ink); font: inherit; text-decoration: underline;
}

/* Q8 ITEM 5 -- FULL PAGE WIDTH (Johann's standing rule). A 1100px column on a
   1920px screen wrapped every cost table and put the total below the fold on a
   quote with twenty-nine job rows, while two thirds of the screen stood empty.
   The gutter stays, because text against a window edge is unreadable, and so
   does a ceiling far above any real screen -- there is a width past which a
   table row is no longer scannable, and that is a different fault. */
main { max-width: 2400px; margin: 1rem auto; padding: 0 1.25rem; }

/* ---- the message area. `ok` and `warn` must never look the same. ---- */
.messages { max-width: 2400px; margin: .75rem auto 0; padding: 0 1.25rem; list-style: none; }
.msg { padding: .45rem .75rem; border-radius: 5px; border: 1px solid; margin: 0 0 .5rem; max-width: var(--pm-measure); }
main .messages { padding: 0; margin: 0 0 .5rem; }
.msg-ok   { background: var(--pm-ok-bg);   color: var(--pm-ok-ink);   border-color: var(--pm-ok-line); }
.msg-warn { background: var(--pm-warn-bg); color: var(--pm-warn-ink); border-color: var(--pm-warn-line); }

/* A PANEL IS FOR A GROUP OF THINGS, NOT FOR ONE THING (Q19, 2). */
.card {
  background: var(--pm-surface); color: var(--pm-ink);
  border: 1px solid var(--pm-line); border-radius: 6px;
  padding: var(--pm-pad-panel); margin-bottom: var(--pm-gap-panel);
}
.card > :last-child { margin-bottom: 0; }
h1, h2, h3 { line-height: 1.25; }
h1 { font-size: var(--pm-fs-title); margin: 0 0 .6rem; }
h2 { font-size: var(--pm-fs-panel); margin: 0 0 .45rem; }
h3 { font-size: var(--pm-fs-sub); margin: .8rem 0 .4rem; }
p { margin: 0 0 .55rem; }
main p, main ul { max-width: var(--pm-measure); }

/* THE TABLES ARE THE BIGGEST WIN (Q19, 2). An accounting grid: tight rows,
   hairline rules, figures right-aligned in tabular numerals, headers quieter
   than the data they head. */
table { border-collapse: collapse; width: 100%; font-size: var(--pm-fs-table); }
th, td {
  text-align: left; vertical-align: baseline;
  padding: var(--pm-row-y) var(--pm-cell-x); border-bottom: 1px solid var(--pm-line-soft);
}
thead th { border-bottom: 1px solid var(--pm-line); }
th { color: var(--pm-ink-soft); font-weight: 600; font-size: var(--pm-fs-note); text-transform: uppercase; letter-spacing: .03em; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
tr.total td, tr.total th { font-weight: 700; border-top: 1px solid var(--pm-ink-faint); border-bottom: 0; }
tbody tr:hover > td { background: var(--pm-surface-2); }
/* Dates, numbers and codes never wrap. */
td.nowrap, th.nowrap { white-space: nowrap; }
td.out { color: var(--pm-out-ink); }
/* A short value centred in a column no wider than it needs (Q33). */
td.centre, th.centre { text-align: center; font-variant-numeric: tabular-nums; white-space: nowrap; width: 1%; }

/* FIELDS ARE SIZED TO THEIR CONTENT, NEVER FULL WIDTH FOR A SHORT VALUE
   (Q19, 3). An input with a `size` takes that width; one without takes a
   name's width. A field that genuinely wants the width says `.grow`. */
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], select, textarea {
  background: var(--pm-surface); color: var(--pm-ink);
  border: 1px solid var(--pm-line); border-radius: 4px;
  padding: 0 var(--pm-input-x); font: inherit;
  height: var(--pm-input-h); max-width: 100%;
}
textarea { height: auto; padding: var(--pm-input-x); }
input[type=text]:not([size]), input[type=email], input[type=password], input[type=number] {
  width: var(--pm-w-name);
}
input[type=date] { width: var(--pm-w-date); }
input:focus, select:focus, textarea:focus, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--pm-accent); outline-offset: 1px;
}
/* A BUTTON WITH NO CLASS IS STILL A BUTTON. The job page's "Same job as"
   rendered in the browser's own grey bevel because nothing styled it. */
button {
  background: var(--pm-surface); color: var(--pm-ink);
  border: 1px solid var(--pm-line); border-radius: 4px;
  padding: .2rem .6rem; font: inherit; font-size: .9rem; cursor: pointer;
  min-height: 1.75rem;
}
button:hover { border-color: var(--pm-ink-soft); }
button.primary {
  background: var(--pm-accent); color: var(--pm-accent-ink);
  border: 1px solid var(--pm-accent); border-radius: 4px;
  padding: 0 .9rem; height: var(--pm-input-h); font: inherit; font-weight: 600; cursor: pointer;
}
button.danger {
  background: var(--pm-warn-bg); color: var(--pm-warn-ink);
  border: 1px solid var(--pm-warn-line); border-radius: 4px;
  padding: .2rem .6rem; font: inherit; font-size: .9rem; cursor: pointer;
}
/* Q12 ITEM 9 — "NOT the same job", beside "Same job as <quote>".

   THE TWO MUST NOT LOOK ALIKE. One joins two quote numbers into a group and
   moves a figure; the other closes a question and moves nothing. A pair of
   identical buttons on the same row is how the wrong one gets clicked, and both
   of them are recorded facts a person signs their name to. Quiet rather than
   red: recording that two jobs are separate is an ordinary, correct answer and
   not a warning. */
button.quiet {
  background: none; color: var(--pm-ink-soft);
  border: 1px solid var(--pm-line); border-radius: 4px;
  padding: .2rem .6rem; font: inherit; font-size: .9rem; cursor: pointer;
}
button.quiet:hover { color: var(--pm-ink); border-color: var(--pm-ink-soft); }

.field { margin-bottom: var(--pm-gap-field); }
.field label { display: block; font-weight: 600; font-size: var(--pm-fs-note); color: var(--pm-ink-soft); margin-bottom: var(--pm-label-gap); }
.field .hint { margin: .2rem 0 0; }
.hint { color: var(--pm-ink-soft); font-size: .85rem; }

/* ---- provenance. Never quiet, never optional. ---- */
.provenance {
  background: var(--pm-muted-bg); color: var(--pm-ink);
  border: 1px solid var(--pm-line); border-left: 4px solid var(--pm-accent);
  border-radius: 5px; padding: .35rem .7rem; margin-bottom: var(--pm-gap-panel);
  font-size: .85rem; max-width: none;
}
.provenance strong { color: var(--pm-ink); }

.greyed { color: var(--pm-ink-soft); }
.exception { color: var(--pm-warn-ink); }
.pill {
  display: inline-block; padding: 0 .45rem; border-radius: 999px;
  background: var(--pm-muted-bg); color: var(--pm-ink-soft);
  border: 1px solid var(--pm-line); font-size: .75rem; line-height: 1.5;
  font-weight: 500; white-space: nowrap; vertical-align: 1px;
}
.foot { max-width: 2400px; margin: 1.5rem auto 1rem; padding: 0 1.25rem; color: var(--pm-ink-soft); font-size: .8rem; }

/* ======================================================================
   THE ADMINISTRATION CONSOLE'S PALETTE
   ======================================================================

   WHAT WENT WRONG, so it is not repeated. The first version of this file
   published `--mkadmin-bg`, `--mkadmin-border` and `--mkadmin-accent-fg` on
   `:root`. **THE CONSOLE READS NONE OF THOSE THREE NAMES.** They were
   invented here rather than read out of the package's own
   `console/templates/_admin_console.css`, and a custom property that nothing
   reads is silent — it does not warn, it simply never applies.

   What the console actually reads is the eleven names below. Five of them
   were never published at all.

   The consequence was black-on-black at `/admin`. `--mk-surface` resolves as
   `var(--mkadmin-surface, var(--card, Canvas))`; Panelman published neither
   `--mkadmin-surface` nor `--card`, so it fell through to the system colour
   `Canvas`. `.mkadmin` declares `color-scheme: light dark`, so on a reader
   whose browser prefers dark, `Canvas` is near-black — while `--mkadmin-fg`
   WAS published, as this application's dark ink. Dark text, dark surface,
   on tabs, cards and every form control. The rest of the application was
   unaffected because nothing outside `.mkadmin` resolves through that chain.

   Set on `.mkadmin` rather than `:root` so the console's palette is one
   block a reader can check against the package's list, instead of being
   mixed into the application's own variables.

   DO NOT ADD A NAME HERE WITHOUT CHECKING IT AGAINST
   `_admin_console.css` — that file is the authority on what is read, and
   guessing is what caused this. */
.mkadmin {
  /* Text. `--mkadmin-fg` is the only one of these the broken version got
     right, which is precisely why it was black on black: correct ink over a
     surface that had fallen through to a system colour. */
  --mkadmin-fg: var(--pm-ink);
  --mkadmin-muted: var(--pm-ink-soft);
  --mkadmin-faint: var(--pm-ink-faint);

  /* Surfaces, and the line between them. `--mkadmin-surface` is the one whose
     absence produced the fault. */
  --mkadmin-surface: var(--pm-surface);
  --mkadmin-surface-2: var(--pm-surface-2);
  --mkadmin-line: var(--pm-line);

  /* Form controls — the role dropdowns among them. A control that sets one
     side inherits the other from the host, so both are stated. */
  --mkadmin-field-bg: var(--pm-surface);
  --mkadmin-field-fg: var(--pm-ink);

  /* THE PAIR THAT MUST NEVER BE SPLIT. The package's note is explicit: one is
     unreadable without the other. This is the same pair the application's own
     topbar and primary buttons use, so a selected tab reads as the same
     control as everything else on the screen. */
  --mkadmin-accent: var(--pm-accent);
  --mkadmin-accent-ink: var(--pm-accent-ink);

  /* "This one deletes something" has to keep meaning that, so it cannot be
     the accent. A red that is legible on this application's white cards. */
  --mkadmin-danger: var(--pm-danger);
}

/* ======================================================================
   Q8 — the walkthrough fixes that are style rather than wording
   ====================================================================== */

/* THE LIVE-LOAD NOTICE (Q8 item 15, ruling 2026-09-17).

   COULD NOT CHECK is the THIRD outcome and it is not the second (house rule
   2026-09-15). It is coloured NEUTRALLY and deliberately not with `.msg-warn`:
   "we could not reach Omni" is a failure of ours, and painting it in the same
   red as a refusal has somebody checking a job number that was right. */
.notice {
  background: var(--pm-muted-bg); color: var(--pm-ink);
  border: 1px solid var(--pm-line); border-left: 4px solid var(--pm-ink-faint);
  border-radius: 5px; padding: .4rem .7rem; margin-bottom: var(--pm-gap-panel);
  font-size: .85rem; max-width: none;
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
}
.notice .hint { flex: 1 1 20rem; }
.button-ish {
  display: inline-flex; align-items: center; padding: 0 .8rem; border-radius: 4px;
  min-height: 1.75rem;
  background: var(--pm-surface); color: var(--pm-ink);
  border: 1px solid var(--pm-line); text-decoration: none; font-size: .9rem;
}
.button-ish:hover { text-decoration: underline; border-color: var(--pm-ink-soft); }
/* Q19: ONE PRIMARY ACTION PER PLACE. A link drawn as a button is neutral
   unless it is the thing the screen is for. On the confirmation page "Go back
   and change something" was the same teal as "Confirm and post". */
.button-ish.primary {
  background: var(--pm-accent); color: var(--pm-accent-ink); border-color: var(--pm-accent);
  font-weight: 600;
}

/* THE ⓘ (Q8 item 3). The technical detail is kept and folded away — a support
   call still needs the run directory and the row counts; the person reading
   the figure does not. `<details>` and not a tooltip, because a tooltip is
   invisible to a keyboard and does not print.

   Q10: the selector is no longer scoped to `.provenance`. The source-error
   screen now puts its diagnostic behind the same marker (item 3 took the
   scheduled pull out of its visible sentence), and a marker that rendered as a
   bare triangle there because the stylesheet only knew about one parent would
   be a second design for the same control. */
.tech { display: inline; }
.tech > summary {
  display: inline; cursor: pointer; list-style: none;
  color: var(--pm-accent); font-size: 1rem;
}
.tech > summary::-webkit-details-marker { display: none; }
.tech > span {
  display: block; margin-top: .5rem;
  color: var(--pm-ink-soft); font-size: .85rem;
}

/* Q12 ITEM 3 — THE COLLAPSED EXCEPTIONS SECTIONS (ruled 2026-09-18).

   The summary carries the section's `<h2>` and its pill, so the closed state
   reads exactly as the open heading did. `list-style: none` plus an explicit
   marker: the default triangle sits before the heading and pushes it, and a
   heading that moves when a section opens reads as a different heading. */
details.section > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: .5rem;
}
details.section > summary::-webkit-details-marker { display: none; }
details.section > summary::before {
  content: "\25B8"; color: var(--pm-accent); font-size: .9em;
  transition: transform .12s ease-in-out;
}
details.section[open] > summary::before { content: "\25BE"; }
details.section > summary > h2 { margin: 0; }
/* Keyboard focus must be visible: the summary IS the control. */
details.section > summary:focus-visible {
  outline: 2px solid var(--pm-accent); outline-offset: 2px;
}

/* THE COLLAPSED MANUFACTURING ROWS (Q8 item 12). */
.collapsible > summary {
  cursor: pointer; padding: .3rem 0; font-weight: 600; color: var(--pm-accent);
}
table.compact th, table.compact td { padding: .15rem .45rem; font-size: .85rem; }

/* Q12 ITEM 1 — the invoices-and-their-quote-numbers table inside the Job card's
   Invoices cell. A table inside a table cell, which is right here because the
   cell holds a list of pairs and not a sentence, and wrong if it draws a second
   box around itself. */
table.inner { margin: 0; border: 0; background: none; }
table.inner th { background: none; font-weight: 600; padding-left: 0; }
table.inner td { padding-left: 0; border-bottom: 0; }
/* One line per row, and the whole description on hover — the `title` attribute
   is on the cell in the template. A manufacturing description is a full line of
   stock text and twenty-nine of them wrapping is what pushed the total off the
   screen. */
td.truncate {
  max-width: 28rem; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}

/* ======================================================================
   Q8 ITEM 21 — the console's untyped inputs, fixed HOST-SIDE
   ======================================================================

   THE FAULT. `admin_user_detail.html` renders the over-and-above grant note as
   `<input name="note" placeholder="why — kept in the audit" size="24">` — with
   NO `type` attribute. The package's own control rule is
   `.mkadmin-field input, .mkadmin input[type="text"], …`, and an input with no
   type attribute matches NEITHER: `input[type="text"]` is an attribute
   selector and an absent attribute does not match it, however the browser
   chooses to behave. So that one input fell through to the user agent, and
   `.mkadmin` declares `color-scheme: light dark` — correctly, because a
   package cannot know its host — so on a reader whose browser prefers dark the
   UA painted it dark, on this deliberately light page. That is what Johann saw.

   WHY IT IS FIXED HERE AND NOT IN THE PACKAGE. Package decision 2026-09-03-S
   makes the host's stylesheet the place a host states the console's colours,
   and this repo never patches the installed package or the vendored copy. The
   selector is `.mkadmin input`, which covers every input the console renders,
   typed or not, and states BOTH SIDES in the one rule as 2026-09-03-T
   requires. It is more general than the package's own rule, so it can only
   fill gaps the package leaves and never fight it.

   THE SELECTOR IS `input:not([type])` AND NOT `input`, deliberately. A bare
   `.mkadmin input` would also repaint checkboxes, radios and buttons, which
   the package styles or leaves to the platform on purpose. `:not([type])`
   matches exactly the elements that fall through the package's own list and
   nothing else — an element cannot both carry a type attribute and lack one,
   so this rule can never contend with the package's for the same input.

   THE UNDERLYING SELECTOR GAP IS STILL THE PACKAGE'S and is raised against it
   in WORKLOG. A host stylesheet is the right place to say what a colour IS; it
   is the wrong place to be the only thing standing between a shipped control
   and a system default, because the next host will not know to write this. */
.mkadmin input:not([type]) {
  background-color: var(--pm-surface);
  color: var(--pm-ink);
  border: 1px solid var(--pm-line);
  border-radius: 5px;
  padding: .35rem .5rem;
  font: inherit;
}

/* THE EXCEPTIONS PAGER (Q9 item 3). The section's count and total live in the
   heading above the table, so they are in view on every page without this
   having to repeat them; this says only where in the list the reader is. */
.pager {
  display: flex; align-items: center; gap: .9rem; flex-wrap: wrap;
  margin: .5rem 0 0; color: var(--pm-ink-soft); font-size: .875rem;
}

/* ======================================================================
   Q10 — THE NEUTRAL STATE PANEL
   ======================================================================

   THE PEACH PANEL IS GONE FROM THE ORDINARY STATES OF THE CLIENT'S OWN DATA
   (ruled 2026-09-17). An invoice with no job behind it, a job with nothing
   billed against it, a quote with no project job, a cancelled group: every one
   of those is a fact about Panelman's data, not an error and not the client's
   fault. `.msg-warn` said otherwise in red on every job page that carried one.

   It is the SAME GREY AS THE DATA BANNER, deliberately and not approximately:
   the same `--pm-muted-bg` over the same line, so the two read as one family
   of "here is something about these figures" rather than two.

   `.msg-warn` KEEPS ITS RED and keeps its job — a refusal, and the staleness
   banner, which is a fault of ours and not a state of theirs. Rendering a
   refusal identically to a statement of fact is the fault this whole palette
   was written around.

   It is used on the login screen too, for the idle sign-out sentence (item 1):
   being signed out at lunch is not a mistake either. */
.state {
  background: var(--pm-muted-bg); color: var(--pm-ink);
  border: 1px solid var(--pm-line); border-left: 4px solid var(--pm-ink-faint);
  border-radius: 5px; padding: .4rem .75rem; margin: 0 0 var(--pm-gap-panel);
  font-size: .9rem;
}

/* ======================================================================
   Q11 — THE INVOICE PANEL, AND THE ⓘ IN A COLUMN HEADING
   ====================================================================== */

/* THE INVOICE, OPENED ON THE PAGE THE DECISION IS MADE ON (item 2, ruled
   2026-09-18). A panel INSIDE the linking card rather than a dialog over it:
   this application carries no JavaScript, so a `<dialog>` could only ever be
   rendered already-open, and one rendered already-open is a panel wearing the
   word dialog. What a person keeps by its being here is the list it came from,
   still on screen, directly beneath it.

   It is set on the card's own surface with a heavier left edge, so it reads as
   something opened out of the list rather than as a second card. */
.invoice-panel {
  border: 1px solid var(--pm-line); border-left: 4px solid var(--pm-accent);
  border-radius: 5px; padding: .6rem .8rem; margin: 0 0 var(--pm-gap-panel);
  background: var(--pm-surface-2);
}
.invoice-head {
  display: flex; align-items: baseline; gap: 1rem; margin-bottom: .5rem;
}
.invoice-head h3 { font-size: 1.05rem; margin: 0; }
.closer { margin-left: auto; font-size: .875rem; }
.invoice-panel table { margin-bottom: .8rem; }
.invoice-panel table:last-of-type { margin-bottom: 0; }

/* A COMMENT LINE IS KEPT AND IT IS NOT A FAULT. It carries no quantity and no
   value by construction, and it is often the only thing on the document that
   says what the work was. Quieter than the priced lines, never struck out and
   never coloured as a problem. */
tr.comment-line td { color: var(--pm-ink-soft); }

/* THE ⓘ INSIDE A COLUMN HEADING (item 7). `th` is uppercase, bold, tracked out
   and right-aligned in the money columns — every one of which is wrong for a
   sentence. The marker inherits the heading; the sentence behind it must not. */
th .tech > span {
  font-weight: 400; text-transform: none; letter-spacing: normal;
  text-align: left; white-space: normal; min-width: 16rem;
}

/* ======================================================================
   Q18 — THE CAPTURE SCREEN LAID OUT LIKE A TILL (2026-09-23)
   ====================================================================== */

/* A ROW OF FIELDS, EACH SIZED TO WHAT IT HOLDS. The general rule above makes
   every text input 100% wide — right for a one-field form, and exactly what
   made a date box 1,200 pixels wide on the capture screen. Inside a row the
   input takes the width its `size` asks for, the label sits on top, and the
   row wraps on a narrow window rather than scrolling sideways. */
.fieldrow {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem 1rem;
  margin-bottom: .6rem;
}
.fieldrow:last-child { margin-bottom: 0; }
.fieldrow .f { display: flex; flex-direction: column; gap: .2rem; }
.fieldrow .f.grow { flex: 1 1 18rem; }
.fieldrow .f.grow input[type=text] { width: 100%; }
.fieldrow .f-end { margin-left: auto; }
.fieldrow label, .fieldrow .label {
  font-weight: 600; font-size: .85rem; color: var(--pm-ink-soft);
}
.fieldrow input[type=text], .fieldrow input[type=date], .fieldrow select {
  width: auto;
  background: var(--pm-surface); color: var(--pm-ink);
  border: 1px solid var(--pm-line); border-radius: 4px; font: inherit;
}
.fieldrow input[type=date] { width: var(--pm-w-date); }
.fieldrow .shown { padding: .3rem 0; }
.fieldrow .state { margin: 0; padding: .35rem .6rem; font-size: .85rem; }
input.num { text-align: right; font-variant-numeric: tabular-nums; }
/* The next-reference line, beside the field and short (1.2). */
.aside { color: var(--pm-ink-soft); font-size: .8rem; max-width: 16rem; }

.capture .card, form .card { padding: var(--pm-pad-panel); margin-bottom: var(--pm-gap-panel); }
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin: 0 0 var(--pm-gap-panel); }

/* THE DEFAULT BUTTON. Enter in a text box presses the FIRST submit button in
   the form, and on the capture screen that must be Check — never Remove or
   the category switch. Off-screen rather than `display: none`, because a
   button that is not rendered is not reliably the default one. */
.default-action {
  position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden;
}

/* READ, NOT FILLED IN (1.5). Label over value, as many to a row as fit. */
dl.dense {
  display: flex; flex-wrap: wrap; gap: .4rem 1.75rem; margin: 0 0 var(--pm-gap-panel);
  background: var(--pm-surface); color: var(--pm-ink);
  border: 1px solid var(--pm-line); border-radius: 6px; padding: var(--pm-pad-panel);
}
dl.dense > div { display: flex; flex-direction: column; }
dl.dense dt {
  color: var(--pm-ink-soft); font-weight: 600; font-size: .75rem;
  text-transform: uppercase; letter-spacing: .03em;
}
dl.dense dd { margin: 0; }
dl.dense dd.num { font-variant-numeric: tabular-nums; }
dl.dense .total dd { font-weight: 700; }

/* ~~THE BANKING MENU IN THE TOP BAR (5.1), a `<details>` dropdown.~~ Q19, 5:
   replaced by the two-level bar (`.sectionbar`, `.screenbar`, above). Its
   rules went with it; `step_check.sh` checks they have not come back. */
/* The narrative history, folded below the capture buttons: its intro is a
   hint, not part of the heading. */
.collapsible > summary .hint { font-weight: normal; }


/* ======================================================================
   Q19 -- THE DESIGN PASS (2026-09-23). LAYOUT, IN ONE PLACE.
   ======================================================================

   Johann walked every screen of the deployed build: "it looks like it's just
   stuck together". Every rule below is a layout templates used to carry, or
   never had. Density and rhythm come from the tokens on `:root`; nothing here
   invents a number of its own that a token already names. */

/* SECTIONS SIDE BY SIDE (3). The front page's cards, as peers. */
.panels {
  display: grid; gap: var(--pm-gap-panel);
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  align-items: start;
}
.panels > .card { margin-bottom: 0; }

/* A SECTION'S LIST OF SCREENS (5.3), on the front page and a section page:
   the link, then its one line. */
ul.screens { list-style: none; padding: 0; margin: 0; }
ul.screens li { padding: .3rem 0; border-bottom: 1px solid var(--pm-line-soft); }
ul.screens li:last-child { border-bottom: 0; }
ul.screens a { font-weight: 600; }
main > ul.screens {
  background: var(--pm-surface); border: 1px solid var(--pm-line);
  border-radius: 6px; padding: .2rem .95rem; margin-bottom: var(--pm-gap-panel);
}

/* A ROW OF LABELLED FIELDS, LABELS ABOVE (3): the filter on the enquiry and
   the audit trail, the grant form on bank accounts, the quote details on a
   job page. Side by side, the button on the same line. The reading screens
   keep their labels above (4). */
form.fieldline {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--pm-gap-field) var(--pm-gap-group);
}
form.fieldline .field { margin: 0; }
form.fieldline .field p { margin: 0; line-height: var(--pm-input-h); }
.card.filters-card { padding-top: .55rem; padding-bottom: .55rem; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* A button in a table row does not make the row taller. */
td button, td button.quiet, td button.danger, td button.primary, td .button-ish {
  min-height: 0; height: auto; padding-top: 0; padding-bottom: 0; line-height: 1.45; font-size: .82rem;
}

/* A HEADING AND A WIDE SEARCH, NO CARD (Find a job, 2 and 3). */
form.search {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--pm-gap-field) .75rem;
  margin: 0 0 var(--pm-gap-panel);
}
form.search input[type=text] { width: min(40rem, 100%); }
form.search .hint { flex-basis: 100%; margin: 0; }

/* LABELS BESIDE FIELDS, ON THE CAPTURE SCREENS ONLY (4, Johann's ruling
   2026-09-23): the capture screen, the split screen and the bank account
   set-up form. Right-aligned in a narrow column, as Omni does it. A `.fieldrow`
   inside `.beside` keeps its markup -- and so its tab order -- and only the
   layout changes: each field becomes a label cell and a value cell. */
.beside .fieldrow {
  display: grid; grid-template-columns: var(--pm-label-col) auto var(--pm-label-col) auto;
  align-items: center; justify-content: start; gap: .35rem .6rem;
  margin-bottom: .35rem;
}
.beside .fieldrow + .fieldrow { padding-top: .35rem; border-top: 1px solid var(--pm-line-soft); }
.beside .fieldrow .f { display: contents; }
.beside .fieldrow label, .beside .fieldrow .label {
  text-align: right; font-weight: 500; font-size: .85rem; color: var(--pm-ink-soft);
}
.beside .fieldrow .f > :not(label):not(.label) { justify-self: start; }
.beside .fieldrow .f.grow input[type=text] { width: 22rem; }
/* The next-reference line sits in the pair of cells beside its field; the
   category switch sits under the field it follows. */
.beside .fieldrow .aside { grid-column: span 2; max-width: 30rem; align-self: center; }
.beside .fieldrow .f-end { margin: 0; }
.beside .fieldrow .f-end > button { grid-column: 2; justify-self: start; }
/* One column of pairs when the window is narrow. */
@media (max-width: 56rem) {
  .beside .fieldrow { grid-template-columns: var(--pm-label-col) auto; }
}
/* A plain `.field` stack, label beside (the bank account set-up form). */
.beside .field {
  display: grid; grid-template-columns: var(--pm-label-col) auto; align-items: center;
  justify-content: start; gap: .6rem;
}
.beside .field label { text-align: right; margin: 0; font-weight: 500; font-size: .85rem; }
.beside .field-actions { padding-left: calc(var(--pm-label-col) + .6rem); }

/* THE CAPTURE SCREEN USES THE WIDTH (3): the transaction on the left, and on
   the right, aligned to the top, the reference material -- the bank account
   and, once a split is open, what is still to allocate. That is where Omni
   puts them. */
.capture-layout {
  display: grid; grid-template-columns: minmax(0, 1fr) 17rem;
  gap: var(--pm-gap-panel); align-items: start;
}
/* First in the markup, so Tab reaches the bank account first; drawn on the
   right. */
.capture-side { grid-column: 2; grid-row: 1; margin: 0; }
.capture-main { grid-column: 1; grid-row: 1; min-width: 0; }
.capture-side dl { margin: 0; }
.capture-side dt {
  color: var(--pm-ink-soft); font-weight: 600; font-size: var(--pm-fs-note);
  text-transform: uppercase; letter-spacing: .03em;
}
.capture-side dd { margin: 0 0 .55rem; }
.capture-side dd.num { font-variant-numeric: tabular-nums; font-size: 1.1rem; font-weight: 600; }
@media (max-width: 64rem) {
  .capture-layout { grid-template-columns: minmax(0, 1fr); }
  .capture-side, .capture-main { grid-column: 1; grid-row: auto; }
}

/* BANK ACCOUNTS: GRANTS AND SET-UP SIDE BY SIDE (3). */
.two-col {
  display: grid; gap: var(--pm-gap-panel);
  grid-template-columns: repeat(auto-fit, minmax(28rem, 1fr)); align-items: start;
}
.two-col > .card { margin-bottom: 0; }

/* A NARROW FORM (sign in, change password): sized to its fields, not to the
   window. */
.card.narrow { max-width: 26rem; }
.card.narrow input[type=email], .card.narrow input[type=password] { width: 100%; }
.card.signin { margin: 3rem auto; }

/* THE EXCEPTIONS SCREEN AS SEVEN ROWS (2; ruled at Step 0: in CSS, not
   markup). Each section is still a `.card` around a `<details>`, so it still
   opens on a click and the tests still find it by its id. What changes is the
   drawing: closed, the sections stack as the rows of one ruled table --
   name, count, total -- with no box around each. Open, the section's own
   table drops in beneath its row. */
.exceptions-list {
  background: var(--pm-surface); border: 1px solid var(--pm-line); border-radius: 6px;
  margin-bottom: var(--pm-gap-panel);
}
.exceptions-list > .card {
  border: 0; border-radius: 0; margin: 0; padding: 0;
  border-bottom: 1px solid var(--pm-line-soft);
}
.exceptions-list > .card:last-child { border-bottom: 0; }
.exceptions-list details.section > summary { padding: .35rem .75rem; align-items: center; }
.exceptions-list details.section > summary:hover { background: var(--pm-surface-2); }
.exceptions-list details.section > summary > h2 {
  font-size: .95rem; font-weight: 600; flex: 1;
  display: flex; align-items: center; gap: .6rem;
}
.exceptions-list details.section > summary > h2 .pill {
  margin-left: auto; font-size: .8rem; font-variant-numeric: tabular-nums;
  background: none; border: 0; color: var(--pm-ink-soft); padding: 0;
}
.exceptions-list details.section[open] { padding-bottom: .6rem; }
.exceptions-list details.section[open] > summary { background: var(--pm-surface-2); }
.exceptions-list details.section > :not(summary) { margin-left: .75rem; margin-right: .75rem; }
.exceptions-list details.section > p.hint { margin-top: .45rem; }

/* ======================================================================
   Q35 — PRODUCTION: THE PROJECT SCREEN'S CHECKS
   ====================================================================== */

/* The checks panel is a short list of plain lines, each starting with its
   own mark (✓, ✗ or ·), so it needs no bullets of its own. */
ul.checks { list-style: none; padding: 0; margin: 0 0 .5rem; }
ul.checks > li { margin: .15rem 0; }
/* A material line in stock: the ok colour, as a confirmation is. A short one
   uses the warning ink only, never the red panel: a shortage is shown, not
   blocking (A3 c). */
.ok { color: var(--pm-ok-ink); }
.short { color: var(--pm-warn-ink); }

/* ======================================================================
   Q36 — THE CLOSE-OUT SCREEN, AND "HANG TIGHT"
   ====================================================================== */

/* B1: the overlay over the whole screen while Omni is read or processed in.
   It covers the page, so nothing on it can be pressed a second time. */
.wait-overlay {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  background: rgba(27, 31, 36, .55);
}
.wait-overlay[hidden] { display: none; }
.wait-box {
  display: flex; align-items: center; gap: .8rem;
  max-width: 28rem; margin: 0 16px; padding: 1rem 1.25rem;
  background: var(--pm-surface); color: var(--pm-ink);
  border: 1px solid var(--pm-line); border-radius: 6px;
  font-size: 1rem; font-weight: 600;
}
.wait-box p { margin: 0; }
.wait-spinner {
  flex: none; width: 1.4rem; height: 1.4rem; border-radius: 50%;
  border: 3px solid var(--pm-line); border-top-color: var(--pm-accent);
  animation: pm-spin .9s linear infinite;
}
@keyframes pm-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .wait-spinner { animation: none; } }

/* B2: the done state is a green line, never a red cross. */
.done-line { color: var(--pm-ok-ink); font-weight: 600; }

/* B3.3: one line per row, nothing wraps; a non-stock or comment line is
   greyed; a row's flags sit on it in the warning ink. */
table.lines td { white-space: nowrap; }
table.lines tr.greyed td { color: var(--pm-ink-soft); }
.flag { display: inline-block; margin-left: .35rem; color: var(--pm-warn-ink); font-size: .85rem; }
.made { display: block; font-size: .85rem; }

/* A card's own answer (house rule, 2026-10-29: every click answers where it
   was made). */
.card-answer { margin: 0 0 .6rem; }

/* B3.7: one finding per line, its action beside it. */
ul.findings { list-style: none; padding: 0; margin: 0; }
ul.findings > li { margin: .3rem 0; }
ul.findings form { display: inline; }

/* ======================================================================
   Q38 — THE CLOSE-OUT AND BACKLOG FIXES (Johann's rulings, 2026-11-01)
   ====================================================================== */

/* B1: a short raw material line stands out — red and bold, with a SHORT tag,
   listed first. Its "(on hand …; shown, not blocking)" stays in the hint
   style after the tag. One line per item. */
.material-short { color: var(--pm-danger); font-weight: 700; }
.material-short .hint { font-weight: normal; }
.tag-short {
  display: inline-block; padding: 0 .3rem; margin: 0 .2rem;
  border: 1px solid var(--pm-danger); border-radius: 3px;
  font-size: .75rem; letter-spacing: .03em;
}

/* B5: the loose invoices open in place under their finding, one line each,
   each with its own button and its own answer. */
ul.invoice-lines { list-style: none; padding: 0; margin: .3rem 0 0 1rem; }
ul.invoice-lines > li { margin: .2rem 0; }
.invoice-line .card-answer { display: inline-block; margin: 0 0 0 .4rem; }

/* ======================================================================
   Q38c — THE QUOTE NUMBER LEADS, ONE LAYOUT (Johann's rulings, 2026-11-01)
   ====================================================================== */

/* B1: the title of every screen about a project, job or quote is the
   internal quote number, large; the customer and the project job sit on the
   same line, smaller. */
h1.quote-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .7rem; }
.quote-lead { font-size: 1.6rem; font-weight: 700; }
.quote-sub { font-size: var(--pm-fs-sub); font-weight: normal; color: var(--pm-ink-soft); }
.quote-sub-line { margin: -.4rem 0 .8rem; color: var(--pm-ink-soft); }

/* B2: a figure row that opens its own form in place (the quote capture on
   the "Original quoted PDF amount" row), and a card heading whose
   explanation is behind its ⓘ. */
td .collapsible > summary, dd .collapsible > summary { font-weight: normal; }
td .collapsible form, dd .collapsible form { margin-top: .4rem; }
h2 .tech, h3 .tech { font-weight: normal; }
.one-line { margin: 0 0 .5rem; }

/* B2: one line per unlinked invoice, on both screens. A closed <details>
   broke the line after its ⓘ in Chromium, putting the buttons on a second
   line (seen in the Q38c browser check, on the close-out's lines too). */
ul.invoice-lines > li { white-space: nowrap; }
.invoice-line > details.tech { display: inline-block; }

/* B5: an unlinked invoice whose description names this quote. */
.names-quote { margin-left: .4rem; color: var(--pm-warn-ink); font-size: .85rem; }

/* Q37: the manufacture tree. A stop row is red; an exploded row is quieter. */
tr.tree-stop > td { background: var(--pm-warn-bg); color: var(--pm-warn-ink); }
tr.tree-exploded > td { color: var(--pm-ink-soft); }
tr.tree-reason textarea { width: 100%; box-sizing: border-box; }
td.tree-depth-1 { padding-left: 1.6em; }
td.tree-depth-2 { padding-left: 2.8em; }
td.tree-depth-3 { padding-left: 4em; }
td.tree-depth-4 { padding-left: 5.2em; }
td.tree-depth-5 { padding-left: 6.4em; }
td.tree-depth-6 { padding-left: 7.6em; }
