/* ─────────────────────────────────────────────────────────────────────────────
   Terrapex printed report — Field Inspection + Field Density Testing.

   816 x 1056 px is US Letter at 96 dpi, so 1 px here is 1/96".  Every measurement
   in GeotechPrintModel.cs is in these same units; if you change the padding or the
   row height, change the constants there to match or the pagination drifts.

   Type is Inter (already loaded by App.razor) with IBM Plex Mono for anything the
   inspector entered, so typed values never sit at the same weight as the labels.
   Colour is the app's own: #E8590C is the UI accent and deliberately does NOT
   appear here — a printed client document uses #1E7F4F for section labels and
   passing figures, #C0392B for below spec.
   ───────────────────────────────────────────────────────────────────────────── */

/* Without this the sheet is 1056 + 34 + 24 = 1114px tall and the last 58px — the
   signature block — falls off the bottom of a Letter page and is clipped. The mockups
   never showed it because their own <style> carried a reset; this file is injected into
   a bare document by GeotechPdfService, so it has to bring its own. */
.rp-doc, .rp-doc * { box-sizing: border-box; }

.rp-doc {
    --rail: 218px;   /* rail width == sketch edge. one number drives both */
    --gut: 38px;     /* the only separator between the columns */
    --ink: #15171C;
    --mute: #A9ADB4;
    --hair: #ECEDEF;
    --line: #E8EAED;
    --rule: #DCDFE3;
    --soft: #CBD0D5;
    --acc: #1E7F4F;
    --bad: #C0392B;
    --writeline: #E4E6E9;   /* the ruled writing line on an empty area */

    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    padding: 28px 16px 80px;
    background: #5C6066;
    font-family: Inter, system-ui, -apple-system, sans-serif;
    -webkit-font-smoothing: antialiased;
}

.rp-sheet {
    width: 816px;
    height: 1056px;
    background: #fff;
    color: var(--ink);
    position: relative;
    overflow: hidden;
    box-shadow: 0 18px 44px rgba(0, 0, 0, .34);
}

.rp-page { position: relative; height: 1056px; padding: 34px 46px 24px; display: flex; flex-direction: column; }

/* ── header ─────────────────────────────────────────────────────────────── */
.rp-head { display: flex; justify-content: space-between; align-items: flex-start; }
.rp-pg { font: 500 9px 'IBM Plex Mono', ui-monospace, monospace; color: var(--mute); letter-spacing: .08em; padding-top: 1px; }
.rp-sheet h1 { margin: 12px 0 0; font: 600 25px/1.1 Inter, sans-serif; letter-spacing: -.022em; }
.rp-sheet h1 small { display: block; font: 600 9px Inter, sans-serif; letter-spacing: .24em; text-transform: uppercase; color: var(--acc); margin-top: 6px; }

/* ── identification band (every page except page 1) ─────────────────────── */
.rp-band { margin-top: 18px; border-top: 1px solid var(--ink); border-bottom: 1px solid var(--hair); padding: 9px 0 4px; }
.rp-band .g { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px 22px; }
.rp-band .fr { padding-bottom: 4px; }

.fr label { display: block; font: 700 7px Inter, sans-serif; letter-spacing: .18em; text-transform: uppercase; color: var(--mute); }
.fr .v { min-height: 16px; font: 400 11.5px/16px 'IBM Plex Mono', ui-monospace, monospace; color: var(--ink); }

/* ── page 1: prose wrapping a floated rail ───────────────────────────────
   The rail FLOATS; it is not a grid column. Grid gives both sides equal height, so
   whenever the rail (nine fields plus a 218px sketch) is taller than the prose beside
   it, the text column ends early and anything after it can only begin BELOW the sketch
   — a white gap opening in the middle of a remarks block. Floating lets the prose wrap
   down the side of the rail and then continue full width under its foot as one
   unbroken flow, which is what remarks should do. flow-root contains the float so the
   section tables below start cleanly. */
.rp-body { display: flow-root; margin-top: 20px; }
.rp-body .rp-rail { float: right; width: var(--rail); margin: 0 0 16px var(--gut); }
.rp-rail .fr { padding: 6px 0 4px; border-bottom: 1px solid #F0F1F2; }
.rp-rlb { font: 700 7.5px Inter, sans-serif; letter-spacing: .2em; text-transform: uppercase; color: var(--acc); margin: 0 0 4px; padding-top: 1px; }

/* 14px, up from 5px. At 5 the label read as attached to the first row rather than
   heading the block. NOTE this is not free: page 1 is paginated to the pixel, so the
   remarks character budgets and SectionLabel in GeotechPrintModel were trimmed in the
   same change to pay for it. Move one without the other and a long report overflows
   the sheet. */
.rp-lb { font: 700 8px Inter, sans-serif; letter-spacing: .24em; text-transform: uppercase; color: var(--acc); margin: 0 0 14px; }
.rp-lb.sp { margin-top: 16px; }
/* Paragraphs are structure. Each one is its own <p> with real spacing between; a single
   newline INSIDE a paragraph is still honoured via pre-line, because an inspector listing
   two items under one heading means those line breaks. */
/* line-height and paragraph spacing are set with the ruling further down, so the
   typed rhythm and the printed rules can never drift apart again. */
.rp-txt { font: 400 12.5px/21px Inter, sans-serif; color: #2B3138; margin: 0; }
.rp-txt p { white-space: pre-line; }
/* Never break a page mid-paragraph, and never leave one line stranded. */
.rp-txt p { orphans: 2; widows: 2; break-inside: avoid; }

/* Square sketch, directly under Contractor. Grid is 2 mm fine / 1 cm major. */
.rp-pad {
    width: var(--rail); height: var(--rail);
    border: 1px solid var(--rule); background: #fff; position: relative; overflow: hidden;
}
/* The grid is an inline SVG child, NOT a background gradient.
   ═══════════════════════════════════════════════════════════════════════════
   It was four stacked linear-gradients at 7.26px. Backgrounds rasterise, and a
   7.26px pitch is far below the threshold where that survives — at PDF scale the
   fine grid lands sub-pixel and most of it disappears, which is why the pad printed
   patchy. Same defect the writing rules had, worse because the pitch is a third the
   size. SVG lines are vector: exact at any zoom, on screen and on paper. */
.rp-grid { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.rp-pad img { z-index: 1; }
.rp-pad img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.rp-north { position: absolute; top: 7px; right: 8px; display: flex; flex-direction: column; align-items: center; gap: 2px; font: 500 8px 'IBM Plex Mono', ui-monospace, monospace; color: var(--mute); letter-spacing: .12em; }
.rp-north svg { width: 9px; height: 13px; display: block; }
/* Preview only. Sits where the signature would be, in the same place and at a size that
   cannot be mistaken for one. Never printed — the renderer never sets ShowSignatureHint. */
.rp-sig .ink-hint {
    position: absolute; left: 4px; bottom: 100%; margin-bottom: 4px;
    font: 400 9px 'IBM Plex Mono', ui-monospace, monospace;
    color: #B9BEC4; letter-spacing: 0; text-transform: none; white-space: nowrap;
}

/* Unused since the N.T.S. label was dropped. Kept so an older archived sheet
   re-rendered from stored JSON still lays out identically. */
.rp-nts { position: absolute; bottom: 6px; right: 8px; font: 400 7.5px 'IBM Plex Mono', ui-monospace, monospace; color: #BCC1C6; letter-spacing: .1em; }

.rp-main { flex: 1; min-height: 0; display: flex; flex-direction: column; }

/* ── component checklist ────────────────────────────────────────────────── */
.rp-checks { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px 18px; }
.rp-checks span { display: flex; align-items: center; gap: 7px; font: 400 10.5px Inter, sans-serif; color: #BFC4C9; }
.rp-checks span.on { color: var(--ink); font-weight: 600; }
.rp-checks i { font: 400 8.5px 'IBM Plex Mono', ui-monospace, monospace; color: #CFD3D7; font-style: normal; width: 13px; }
/* The tick, and nothing else.
   ═══════════════════════════════════════════════════════════════════════════
   This was a 10px box filled with var(--ink) and a knocked-out white tick — the
   only solid black fill left anywhere on the sheet, and the heaviest mark on the
   page for a line that is metadata. It out-weighed the failing compaction figure,
   which is the one thing that should be loudest.

   Now an unticked component prints no marker at all: twelve empty squares saying
   "not this one" were never carrying information, the grey label already says it.
   The 10px slot is KEPT on every row so the four columns stay aligned whether a
   component is ticked or not — remove the width and the labels jump left. */
.rp-bx { width: 10px; height: 10px; flex: none; position: relative; }
.rp-bx.on::after {
    content: ""; position: absolute; left: 2.5px; top: 0; width: 4px; height: 7.5px;
    border: solid var(--ink); border-width: 0 1.5px 1.5px 0; transform: rotate(42deg);
}

/* ── tables ─────────────────────────────────────────────────────────────── */
.rp-sheet table { width: 100%; border-collapse: collapse; }
.rp-sheet th {
    font: 700 6.5px/1.35 Inter, sans-serif; letter-spacing: .09em; text-transform: uppercase;
    color: var(--mute); text-align: center; vertical-align: bottom;
    padding: 0 4px 6px; border-bottom: 1.5px solid var(--ink);
}
.rp-sheet td {
    height: 22px; border-bottom: 1px solid var(--hair); padding: 0 4px;
    text-align: center; vertical-align: middle;
    font: 400 11px 'IBM Plex Mono', ui-monospace, monospace; color: var(--ink);
}
.rp-sheet td.no { color: var(--mute); width: 26px; }
.rp-sheet td.txt { font: 400 10.5px Inter, sans-serif; }
.rp-sheet td.loc { font: 400 10px 'IBM Plex Mono', ui-monospace, monospace; }
.rp-sheet td.na { color: #C8CED4; }
/* Scoped to .rp-sheet td so they can actually win.
   ═══════════════════════════════════════════════════════════════════════════
   These were `.rp-pass` and `.rp-fail` — specificity 0,1,0 — while `.rp-sheet td`
   above is 0,1,1 and sets `color: var(--ink)`. The td rule won every time, so a
   failing compaction printed the same black as a passing one. The classes were on
   the cells all along; nothing could see them. The single most important number on
   a density sheet, silently uncoloured. */
/* RED MEANS OUT OF SPEC. NOTHING MEANS IN SPEC.
   ═══════════════════════════════════════════════════════════════════════════
   .rp-pass deliberately paints NOTHING — in-spec figures print as ordinary ink.
   A sheet where most values are green trains the eye to skim past colour, which
   is exactly the wrong instinct when one red number is the whole point of the
   page. One colour, one meaning: something here needs attention.

   The class is still emitted by the component. Neutralising it here rather than
   deleting it from the markup keeps "make passes green again" a one-line change,
   and keeps the preview and the PDF identical, since both read this file. */
.rp-sheet td.rp-pass, .rp-sheet th.rp-pass, .rp-pass { color: var(--ink); font-weight: 400; }
.rp-sheet td.rp-fail, .rp-sheet th.rp-fail, .rp-fail { color: var(--bad); font-weight: 600; }

/* Vertical rules on the density results table only — the gauge and materials
   tables stay horizontal hairlines. */
.rp-sheet table.vr th, .rp-sheet table.vr td { border-left: 1px solid var(--rule); }
.rp-sheet table.vr th:first-child, .rp-sheet table.vr td:first-child { border-left: 0; }

/* Repeated header where the reference type changes. Soft rule, not a table rule:
   it marks a change of frame, not the start of a new table. */
.rp-sheet tr.split th { padding-top: 10px; border-top: 1px solid var(--soft); border-bottom: 1px solid var(--soft); }

/* ── ruled writing areas ─────────────────────────────────────────────────
   A prose block prints as a ruled field: writing lines behind the text, continuing
   past where the writing stops, the way a paper field book does. Empty blocks were
   already ruled; now filled ones are too, and the text sits ON the rules.

   PITCH CORRECTION (2026-08-03). The old note here claimed 26px matched the 1.65
   line-height of .rp-txt at 12.5px. It does not — that is 20.625px, a 5.4px error
   per line. It never showed because ruled and typed lines never appeared together;
   now that they do, 5.4px compounds and the text walks off the rules inside one
   paragraph. Both are now 21px, and paragraph spacing is exactly one rule so the
   rhythm survives a paragraph break. Blank-line counts in the component were raised
   to keep an empty block the same overall height as before.

   Only .rp-txt is ruled. Rail fields, the sketch pad and every table are untouched —
   they are structured data, not somewhere anyone writes. */
/* flow-root is load-bearing, not tidiness. A plain block beside a float still spans the
   FULL container width — only its line boxes are shortened — so a border-bottom would
   draw straight under the floated rail and vanish behind the white sketch pad. Giving
   the ruled block its own formatting context shrinks it to the space actually left
   beside the float, so the rules land exactly where the writing would. */
.rp-lines { display: flow-root; }
.rp-lines .ln { height: 21px; border-bottom: 1px solid var(--writeline); }

/* Typed lines now match the rule pitch exactly. */
.rp-txt { line-height: 21px; }
.rp-txt p { margin: 0 0 21px; }          /* a paragraph break is one blank rule */
.rp-txt p:last-child { margin-bottom: 0; }

/* RULED MODE — real 1px elements, NOT a background gradient.
   ═══════════════════════════════════════════════════════════════════════════
   The first version drew the rules with repeating-linear-gradient. On a Retina
   screen that rasterises at 2x and every line lands cleanly, so the preview looked
   right — but a PDF viewer at 95 % samples near 1x, lines fall between sample rows,
   and roughly every third one disappears. That is a rasterisation artefact of using
   a BACKGROUND for something that has to survive arbitrary zoom.

   Borders are vector in a PDF. They never drop, at any zoom, in any viewer, on any
   printer. The original .rp-lines .ln was already doing it correctly; this restores
   that approach and simply layers the writing on top.

   Structure (see GeotechPrintDocument.Prose):
     .rp-write            the block; flow-root so it shrinks beside the floated rail
       .rp-rules          absolutely filling it, clipped — a stack of 1px .ln borders
       .rp-txt            the writing, painted above

   Because .rp-rules is clipped to the block, the line count self-adjusts: a short
   entry shows the rules down to min-height, a long one down to wherever the text
   ends. Nothing has to count lines. */
.rp-write { position: relative; display: flow-root; overflow: hidden; }

.rp-rules { position: absolute; top: 0; left: 0; right: 0; bottom: 0; overflow: hidden; }
.rp-rules .ln { height: 21px; border-bottom: 1px solid var(--writeline); }

/* Above the rules, and transparent so they show through. */
.rp-write .rp-txt { position: relative; z-index: 1; background: transparent; }

/* NOTE — there is deliberately no rule here switching backgrounds off for the
   identification band, the sketch pad or tables. The first version had one, and it set
   background-image:none on .rp-pad — which is how the sketch pad draws its own 2 mm /
   1 cm grid, so turning rules ON silently erased the sketch grid. Ruling is applied to
   .rp-txt and nowhere else, so nothing needs un-ruling. */

/* An empty field prints a rule to write on. Rail fields already have their own row
   rule, so they're left alone. */
.rp-band .fr .v.blank { border-bottom: 1px solid var(--writeline); }

/* With the rail floated, ruled lines wrap around it and continue beneath it in the
   same 21px rhythm on their own — no height matching needed. This only has to stop the
   first block after the float from adding a top margin and breaking the rhythm. */
.rp-main > .rp-lines:first-child,
.rp-main > .rp-write:first-child { margin-top: 0; }

/* Tables end OPEN: no closing rule on the last row, so the column dividers run off the
   bottom edge and the table reads as "add another" rather than "that's all there is".
   Only applied when the table has spare rows — a table filled to its last row closes
   normally, because there is nothing more to add on that page. */
.rp-sheet table.open-foot tbody tr:last-child td { border-bottom: 0; }

/* ── section spec strip + verdict ────────────────────────────────────────
   Shared by the steel fibre and caisson sections. The strip carries the values that
   apply to the whole section (design dosage, tolerances, bearing capacity); the verdict
   line under the table carries what was COMPUTED from it. Keeping them visually
   distinct matters: one is what the job specified, the other is what was found. */
.rp-spec { display: flex; gap: 26px; padding: 8px 0 9px; border-bottom: 1px solid var(--hair); flex-wrap: wrap; }
.rp-spec div { font: 400 11.5px 'IBM Plex Mono', ui-monospace, monospace; }
.rp-spec span { display: block; font: 700 7px Inter, sans-serif; letter-spacing: .16em; text-transform: uppercase; color: var(--mute); margin-bottom: 3px; }

.rp-verdict { display: flex; justify-content: space-between; align-items: baseline; padding: 8px 0 0; border-top: 1.5px solid var(--ink); margin-top: -1px; }
.rp-verdict .l { font: 700 8px Inter, sans-serif; letter-spacing: .2em; text-transform: uppercase; color: var(--mute); }
.rp-verdict .r { font: 700 12px Inter, sans-serif; letter-spacing: .14em; text-transform: uppercase; color: var(--acc); }
.rp-verdict .r.bad { color: var(--bad); }

/* The average row closes the fibre table off from its samples. */
.rp-sheet tr.avg td { border-top: 1.5px solid var(--ink); font-weight: 600; }

/* ── carry-over + signatures ────────────────────────────────────────────── */
.rp-cont { padding-top: 12px; text-align: right; font: 700 8px Inter, sans-serif; letter-spacing: .2em; text-transform: uppercase; color: var(--acc); }
.rp-sig { margin-top: auto; display: grid; grid-template-columns: 1fr 1fr; gap: 30px; padding-top: 36px; }
.rp-sig > div { position: relative; border-top: 1.5px solid var(--ink); padding-top: 5px; min-height: 14px; font: 700 7.5px Inter, sans-serif; letter-spacing: .2em; text-transform: uppercase; color: var(--mute); display: flex; justify-content: space-between; align-items: baseline; }
.rp-sig .who { font: 400 10px 'IBM Plex Mono', ui-monospace, monospace; letter-spacing: .02em; text-transform: none; color: var(--ink); }
/* A saved signature sits ON the rule, overlapping it slightly, the way a pen would —
   not in a box above it. The image is trimmed to its ink when saved, so height alone
   scales it and the width follows. max-width keeps a very wide scrawl from colliding
   with the printed name on the other side. */
.rp-sig .ink {
    position: absolute; left: 4px; bottom: 100%;
    height: 34px; width: auto; max-width: 230px;
    object-fit: contain; object-position: left bottom;
    margin-bottom: -6px;
}

.rp-sig .sig-placement {
    position: absolute; left: 4px; bottom: 100%; margin-bottom: -6px;
    display: block; width: max-content; height: 34px;
}
.rp-sig .sig-placement .ink {
    position: static; display: block; height: 100%; margin: 0;
}
.rp-sig .sig-placement.sized .ink { width: 100%; max-width: none; object-fit: fill; }
.pv.editing .sig-placement.draggable {
    cursor: grab; touch-action: none; outline: 1px dashed #646d75;
}
.pv.editing .sig-placement.dragging { cursor: grabbing; outline-style: solid; }
.sig-resize { display: none; }
.pv.editing .sig-resize {
    display: block; position: absolute; width: 24px; height: 24px;
    padding: 0; border: 0; background: transparent; touch-action: none;
}
.pv.editing .sig-resize::after {
    content: ''; position: absolute; inset: 7px; border: 1px solid #646d75;
    background: #fff; border-radius: 1px;
}
.pv.editing .sig-resize:focus-visible { outline: 2px solid #1765ba; outline-offset: 1px; }
.sig-n { top: -12px; left: calc(50% - 12px); cursor: ns-resize; }
.sig-e { right: -12px; top: calc(50% - 12px); cursor: ew-resize; }
.sig-ne { right: -12px; top: -12px; cursor: nesw-resize; }
@media print {
    .pv.editing .sig-resize { display: none; }
    .pv.editing .sig-placement.draggable { outline: none; }
}

/* ── screen-only toolbar ────────────────────────────────────────────────── */
.rp-bar { position: sticky; top: 0; z-index: 20; width: 100%; display: flex; align-items: center; gap: 10px; padding: 12px 18px; background: #191C20; color: #AEB5BD; }
.rp-bar .name { font: 600 12px Inter, sans-serif; letter-spacing: .04em; color: #E9EDF1; margin-right: auto; }
.rp-bar button, .rp-bar a { font: 600 12.5px Inter, sans-serif; color: #E8590C; background: none; border: none; cursor: pointer; padding: 6px 4px; text-decoration: none; }

/* Ruled / Plain. A state toggle, not an action, so it reads as a quiet chip rather
   than another orange verb next to Print. Screen only — .rp-bar is not printed. */
.rp-bar button.rp-toggle {
    color: #AEB5BD; border: 1px solid #343A41; border-radius: 100px;
    padding: 5px 13px; margin-left: 4px;
}
.rp-bar button.rp-toggle:hover { color: #E9EDF1; border-color: #4A525B; }
.rp-bar button.rp-toggle.on { color: #E9EDF1; border-color: #5C666F; background: #22262B; }
.rp-bar button:hover, .rp-bar a:hover { text-decoration: underline; }

/* ── print ──────────────────────────────────────────────────────────────── */
@page { size: Letter portrait; margin: 0; }

@media print {
    html, body { margin: 0; padding: 0; background: #fff; }
    .rp-bar, .rp-noprint { display: none !important; }
    .rp-doc { padding: 0; gap: 0; background: #fff; display: block; }
    .rp-sheet { box-shadow: none; break-after: page; page-break-after: always; }
    .rp-sheet:last-child { break-after: auto; page-break-after: auto; }
    /* Keep the greens and reds — without this most browsers drop them. */
    * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   CONCRETE SHEET
   Additions only — every rule above is shared with the geotech sheet and is
   reused as-is. What is here is the handful of things concrete has and geotech
   does not: the revision line, the cast line, the measured pairs, and the
   result columns.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Revision line under the title. Two PDFs of the same job end up in the same
   folder on the client's side; without this nobody can tell which is current. */
.rp-rev {
    display: inline-flex; align-items: center; gap: 7px; margin-top: 7px;
    font: 700 7px Inter, sans-serif; letter-spacing: .18em; text-transform: uppercase;
    color: var(--mute); border: 1px solid var(--rule); border-radius: 2px; padding: 3px 8px;
}
.rp-rev b { color: var(--ink); font-weight: 700; }
.rp-rev.final { border-color: var(--acc); color: var(--acc); }
.rp-rev.final b { color: var(--acc); }

/* Location is free text in a band of short values — give it the rest of its row
   so a long description sets on one line instead of wrapping in a fifth-width
   cell. */
.rp-band .fr.wide { grid-column: span 2; }
.rp-band .fr.wide .v { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The cast moment, stated once above the cylinder table. Cast date and mould are
   identical on every cylinder in a set, so as columns they repeated themselves
   four times. */
.castline {
    font: 400 11px 'IBM Plex Mono', ui-monospace, monospace;
    color: var(--mute); margin: 0 0 7px; letter-spacing: .01em;
}
.castline b { color: var(--ink); font-weight: 500; }

/* Measured on site: label · time · value · specified. Each test keeps its OWN
   time because slump, air and temperature are three separate moments on site. */
.cp-pairs { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--gut); }
.cp-pair {
    display: flex; align-items: baseline; gap: 10px;
    padding: 7px 0; border-bottom: 1px solid var(--hair);
}
.cp-pair .k { font: 400 11px Inter, sans-serif; color: var(--mute); flex: 1; }
/* 38px could not hold "7:52 AM" at 10px mono — about 48px of glyphs — so every time
   broke after the minutes and printed the meridiem on a second line, which also made
   those rows taller than their neighbours. 54px with nowrap; the extra 16px comes out
   of .k, which is elastic. */
.cp-pair .t { font: 400 10px 'IBM Plex Mono', monospace; color: var(--mute); width: 54px; text-align: right; white-space: nowrap; }
.cp-pair .v { font: 500 12.5px 'IBM Plex Mono', monospace; font-variant-numeric: tabular-nums; }
.cp-pair .s { font: 400 10px 'IBM Plex Mono', monospace; color: var(--mute); width: 56px; text-align: right; }

/* Six tasks, one row. */
.cp-checks { grid-template-columns: repeat(6, 1fr); }
/* The concrete sheet's technician-tasks row, same marker.
   This is a SECOND definition of .rp-bx.on and it is more specific than the one in
   the shared block above (.rp-sheet .rp-bx.on beats .rp-bx.on), so it wins on BOTH
   sheets — geotech included. Changing only the shared rule would have looked like
   nothing happened. Kept here rather than deleted because the concrete sheet may
   want its own treatment one day; for now it matches. */
.rp-sheet .rp-bx { width: 10px; height: 10px; border: none; flex: none; position: relative; }
.rp-sheet .rp-bx.on { background: none; }
.rp-sheet .rp-bx.on::after {
    content: ""; position: absolute; left: 2.5px; top: 0; width: 4px; height: 7.5px;
    border: solid var(--ink); border-width: 0 1.5px 1.5px 0; transform: rotate(42deg);
}
.rp-sheet .rp-checks span.on { color: var(--ink); font-weight: 600; }

/* Result columns. A cylinder not yet broken prints "pending", not a blank — a
   blank reads as missing data, pending says it exists and its day has not come. */
.rp-sheet table.res td { height: 26px; font-size: 12px; }
.rp-sheet table.res td.mpa { font-weight: 600; font-size: 13.5px; font-variant-numeric: tabular-nums; }
/* Concrete keeps its green — by decision, and it is a different case from the density
   sheet. There, most rows pass and green everywhere trained the eye past colour. Here a
   report carries three or four cylinders and the whole point of the final issue is
   whether the concrete made strength, so the verdict earns a colour either way.
   Red is applied only at the ACCEPTANCE age — see PctClass in ConcretePrintDocument. */
.rp-sheet table.res td.pct { font-weight: 600; font-variant-numeric: tabular-nums; color: var(--acc); }
.rp-sheet table.res td.pct.bad { color: var(--bad); }
/* An early break carries no verdict — it is data on the curve, not a pass or a fail. */
.rp-sheet table.res td.pct.early { color: var(--ink); font-weight: 400; }
.rp-sheet table.res td.due { color: var(--mute); font-size: 10.5px; }
/* Field issue: results not in yet. Same weight as .due so the row does not shout an
   absence — it is the expected state of a field report, not a problem. */
.rp-sheet table.res td.pending { color: var(--mute); font-size: 10.5px; }
.rp-sheet table.res td.pending,
.rp-sheet table.res tr.pend td { color: #8C939B; font-weight: 400; font-size: 11.5px; }

/* ═══════════════════════════════════════════════════════════════════════════
   LIVE PREVIEW
   The full-screen sheet viewer, shared by both report types. Same shape as the
   borehole log viewer so nothing new has to be learned.
   ═══════════════════════════════════════════════════════════════════════════ */
.pv { position: fixed; inset: 0; z-index: 200; background: rgba(20,22,26,.55);
    backdrop-filter: blur(3px); display: flex; flex-direction: column; }
.pv-head { display: flex; align-items: center; gap: 12px; padding: 11px 18px;
    background: #191C20; color: #E9EAEC; flex-wrap: wrap; }
.pv-id .t { font: 700 13px Inter, sans-serif; }
.pv-id .s { font: 400 11.5px Inter, sans-serif; color: #8B9199; margin-top: 1px; }
.pv-head .sp { flex: 1; }
.pv-b { border: 1px solid #33383F; background: #20242A; color: #C9CDD2; border-radius: 8px;
    padding: 7px 12px; font: 600 12px Inter, sans-serif; cursor: pointer;
    display: inline-flex; align-items: center; gap: 7px; transition: all .15s; }
.pv-b:hover:not(:disabled) { color: #fff; border-color: #4C545D; }
.pv-b.on { background: #E9EDF1; color: #14171A; border-color: #E9EDF1; }
.pv-b.busy { color: #8B9199; pointer-events: none; }
.pv-b.done { color: #7BC9A0; border-color: #2F5C46; }
.pv-b svg { width: 14px; height: 14px; }
.pv-z { display: inline-flex; align-items: center; gap: 2px; }
.pv-z .pct { font: 500 11.5px 'IBM Plex Mono', monospace; color: #8B9199;
    min-width: 42px; text-align: center; }
.pv-body { flex: 1; overflow: auto; padding: 24px; display: flex;
    justify-content: center; align-items: flex-start; }
.pv-sheet { transform-origin: top center; transition: transform .18s; }
.pv-foot { padding: 11px 18px; background: #191C20; color: #8B9199;
    font: 500 11.5px Inter, sans-serif; }
.pv-foot b { color: #E9EAEC; }

/* Editable values. Off, the sheet is exactly the print output. On, every value that can
   be changed gets a faint warm tint — so what is editable is visible without clicking. */
.sheet-ed { outline: none; border-radius: 2px; transition: background .14s, box-shadow .14s; }
.pv.editing .sheet-ed { background: #FBFBF7; box-shadow: 0 0 0 1px #E4E4DE; cursor: text; }
.pv.editing .sheet-ed:hover { background: #F6F6EF; }
.pv.editing .sheet-ed:focus { background: #fff; box-shadow: 0 0 0 2px #E8590C; }

/* Where a sheet edit landed in the form behind it. */
.sheet-landed { animation: sheet-land .85s ease-out; }
@keyframes sheet-land {
    0%   { box-shadow: 0 0 0 0 rgba(232,89,12,.30); }
    35%  { box-shadow: 0 0 0 4px rgba(232,89,12,.16); }
    100% { box-shadow: 0 0 0 0 rgba(232,89,12,0); }
}

@media (max-width: 900px) {
    .pv-body { padding: 10px; }
    .pv-sheet { transform-origin: top left; }
    .pv-id .s { display: none; }
}

/* ── signature placement ───────────────────────────────────────────────────
   Only draggable while the sheet is in edit mode. The grab cursor and the ring
   are the only affordance — nothing is drawn on the printed sheet. */
.rp-sig .ink.draggable { cursor: grab; touch-action: none; }
.rp-sig .ink.draggable:hover { outline: 1px dashed #C9CDD2; outline-offset: 3px; }
.rp-sig .ink.dragging { cursor: grabbing; outline: 1px solid #E8590C; outline-offset: 3px; opacity: .85; }
.pv-tip { font: 500 11px Inter, sans-serif; color: #8B9199; }
