/* ═══ THE CUSTOMER DOCUMENT STYLESHEET (§112.856) ═══════════════════════════════════════
 * Cut verbatim out of full-flow.html so the document renders identically wherever it is
 * loaded. See invoice-doc.js for why it moved. Both files are loaded by full-flow.html and
 * by approve-invoice.html; a new surface loads them, it does not copy them.
 *
 * --orange is the only app variable these rules use; a host page must define it.
 */

/* §112.718 — the rules are scoped to .inv-doc, not #invoicePdfDoc, because the SAME
   document now renders on three surfaces: the print/PDF node (#invoicePdfDoc carries the
   class), the in-app Customer View (.inv-doc-card wrapper on the black page), and the
   Review & Approve scroll box. One markup builder (buildCustomerDocHtml), one stylesheet.
   Layout is the one Joel approved off six mock rounds on 2026-08-13: every business fact
   its own line, logo on the RIGHT, INVOICE + number one flush-left row, NO status pill,
   Service Date / Paid-Due-Valid-Until row between Bill To and the table, totals labels
   hard left, tight vertical rhythm, five-part Service Summary at the bottom. */
.inv-doc{background:#fff;color:#111827;font-size:11pt;line-height:1.3;
  font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;}
/* ⛔ THE LOGO IS ALWAYS TOP RIGHT, AND THE NAME WRAPS TO MAKE ROOM (§112.5642, 2026-08-27).
   Per Joel, on a phone-width invoice where the logo had dropped underneath the address:
   "we always want the logo on the top right, and if the name of the customer is long it
   should just wrap, because now there's just massive space."

   ⚠ THIS CORRECTS §112.542's `flex-wrap:wrap`, WHICH IS WHAT PUT THE LOGO THERE. That rule was
   written for the right reason -- this document is reached through window.print(), and on a
   phone that IS the OS sheet where Save as PDF lives, so it has to survive a narrow render --
   but `flex-wrap` solves narrowness by moving the SECOND ITEM TO A NEW LINE, and here the second
   item is the logo. Wrapping is decided on each item's HYPOTHETICAL size (its content width),
   not on the shrunk size, so `min-width:0` never gets a chance: "Utah Valley Window Cleaners" at
   17pt is ~300px, plus a 120px logo and a 14px gap is ~434px against a ~340px card, so the logo
   dropped to line two every time -- leaving the tall empty column beside the address that Joel
   circled.

   `nowrap` keeps them on one line and makes the TEXT give way instead, which is what he asked
   for: the logo is `flex:0 0 auto` so it holds its size, and the facts column is `flex:1 1 auto`
   with `min-width:0`, so a long business name wraps to two or three lines under a logo that has
   not moved. Narrow rendering is still handled -- by wrapping the words rather than the layout.
   `overflow-wrap:break-word` is the floor for one unbreakable token (a long email address). */
.inv-doc .ip-top{padding-bottom:10px;border-bottom:2px solid #111827;}
.inv-doc .ip-top-row{display:flex;flex-wrap:nowrap;justify-content:space-between;align-items:flex-start;gap:10px 14px;}
.inv-doc .ip-top-row > *{min-width:0;}
.inv-doc .ip-top-row > div{flex:1 1 auto;overflow-wrap:break-word;}
.inv-doc .ip-logo{max-height:80px;max-width:120px;object-fit:contain;display:block;flex:0 0 auto;}
.inv-doc .ip-biz-name{font-size:17pt;font-weight:800;letter-spacing:-0.01em;margin-bottom:3px;overflow-wrap:break-word;}
.inv-doc .ip-biz-line{font-size:9.5pt;color:#111827;}
/* §112.862 — TWO children now (title + date), and they cannot collide: one fixed word hard
   left, one short date hard right, neither able to grow. flex-wrap stays as a floor for an
   absurdly large text-size setting, but at every real width this is one clean row. */
.inv-doc .ip-title-row{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 10px;margin-top:8px;}
.inv-doc .ip-title{font-size:23pt;font-weight:800;letter-spacing:0.02em;line-height:1;}
/* §112.862 — the number is its OWN line under the title now, not a third item in that row.
   Left aligned under the word it belongs to; it is the only one of the three that varies in
   length, so it is the one that gets the room. */
.inv-doc .ip-num{font-size:10.5pt;color:#111827;margin-top:1px;}
/* §112.859 — the document's own date, on the title row. margin-left:auto pushes it to the
   right edge of that row on paper; the row already wraps, so on a narrow phone it drops under
   the title instead of squeezing the number. Black, same weight family as .ip-num (rule 1). */
.inv-doc .ip-topdate{font-size:10.5pt;font-weight:700;color:#111827;margin-left:auto;white-space:nowrap;}
/* §112.863 — the number and the date share the LAST line of the header block so the date sits
   on the rule instead of a line clear of it. baseline alignment keeps the two sitting on the
   same invisible line even though the date is the heavier of the pair. */
.inv-doc .ip-numrow{display:flex;align-items:baseline;gap:4px 12px;margin-top:1px;}
/* §112.719 second pass, per Joel on the generated PDF: "the service date and then the paid
   date on the same row as the customer name — we have plenty of room for it there."
   ONE flex-wrap container: at paper width Bill To and the two date columns share a row;
   on a narrow phone the basis sums past the width, the date pair wraps to its own
   full-width row and spreads — which is byte-for-byte the phone layout Joel approved
   earlier the same day. The room decides, not a media query. */
/* §112.722 — A BLACK LINE UNDER EACH HALF, per Joel on the phone card: "I'd have a black
   line between these and give them a little bit more space vertically." He drew one rule
   between Bill To and the dates and one under the dates.
   THE BORDERS SIT ON THE TWO CHILDREN, NOT THE CONTAINER, and the column gap is 0 with the
   spacing moved into Bill To's own padding-right. That is what makes ONE set of rules read
   correctly at BOTH widths with no media/container query (which could not work anyway: the
   PDF is laid out in an 816px offscreen div inside a phone-sized viewport, so a viewport
   media query would be false exactly when the paper layout needs it):
     · stacked (phone) — Bill To's bottom border IS the line between the two, and the dates
       pair's bottom border is the line under them. Exactly the two rules he drew.
     · side by side (paper) — both children stretch to the same height, so their bottom
       borders meet across the 0 gap and render as ONE unbroken line across the document.
   align-items:stretch is load-bearing for that second case; flex-start left the two borders
   at different heights. Vertical air comes from the children's 10px padding. */
/* §112.861 — NOWRAP, WAS WRAP. With wrap, flex items wrap BEFORE they shrink: the moment
   Bill To's own max-content width plus the status exceeded the line, the status dropped to its
   own row again — which is precisely the wasted row Joel pointed at, reappearing only for
   customers with longer names. Caught by rendering an estimate for "Farmington Physical
   Therapy" after the invoice for "Amanda Torres" already looked right; ONE test row is not a
   test. With nowrap, Bill To (`min-width:0`) absorbs the pressure by wrapping its own text and
   the status never leaves the row. */
.inv-doc .ip-meta2{display:flex;flex-wrap:nowrap;justify-content:space-between;align-items:stretch;
  gap:0;margin:0 0 12px;}
.inv-doc .ip-meta2 > *{min-width:0;}
/* §112.861 — THE STATUS SITS ACROSS FROM THE CUSTOMER'S NAME, NOT ON ITS OWN ROW.
   Per Joel: "Paid date doesn't need its own row. It can be right across from their name on the
   right side — that's a massive amount of space wasted."
   ⚠ HE IS DESCRIBING A HOLE THE PREVIOUS PASSES DUG. §112.858 gave the block a 250px basis
   because it held FOUR cells; at 390px `240 + 250` overflows, so it wrapped to its own
   full-width row. §112.860 cut it to ONE cell — about 130px of content — and THE 250px BASIS
   STAYED BEHIND, so a narrow strip of text kept claiming a whole row while the right half of
   Bill To's row sat empty. Nobody re-measured the basis after the thing it was sized for went
   away. **When you remove content from a flex child, re-check the basis that was sized for it.**
   Bill To flexes; the status is `0 1 auto` — its own content width — pushed right with
   margin-left:auto. They share ONE row at 390px and at 816px alike, so there is no wrap case
   and no second row to lose, and the two bottom borders meet across the gap as one line under
   both: the §112.722 paper behaviour arriving at every width instead of only the wide one. */
.inv-doc .ip-billto{flex:1 1 auto;min-width:0;padding:10px 20px 10px 0;border-bottom:1.5px solid #111827;
  overflow-wrap:anywhere;}
/* §112.858 grid, ~~four cells~~ — since §112.860 THIS BLOCK HOLDS EXACTLY ONE CELL: the job's
   current state (Approved / Not Approved / Paid, or an estimate's Valid Until). The two-column
   grid and the nth-child(even) alignment below are kept because they cost nothing and are what
   a second cell would need, but `.one` is the path every real render takes today. */
/* §112.863 — align-content:END, was START. Bill To's three lines set this row's height and the
   status stretched to match, so its two lines sat at the TOP of a taller box with a gap of dead
   air beneath — Joel: "right now they're kind of floating, these usually sit flush right above
   the line below them." Bottom-aligned, the date lands level with Bill To's own last line and
   the same 10px above the shared rule, so nothing floats and nothing is out of step. */
.inv-doc .ip-dates-pair{flex:0 0 auto;margin-left:auto;display:grid;grid-template-columns:1fr 1fr;
  gap:8px 20px;align-content:end;padding:10px 0;border-bottom:1.5px solid #111827;}
.inv-doc .ip-dates-pair > *{min-width:0;}
.inv-doc .ip-dates-pair > *:nth-child(even){text-align:right;}
/* §112.860 — a LONE cell takes the whole block and hugs the right edge. Left in a 1fr 1fr grid
   it would sit mid-page on paper with an empty column beside it: white area doing nothing, and
   the class is set by the builder rather than by :has() so nothing depends on selector support
   inside the PDF rasteriser. */
.inv-doc .ip-dates-pair.one{grid-template-columns:1fr;}
.inv-doc .ip-dates-pair.one > *{text-align:right;}
/* §112.858 — the clock under an Approved / Not Approved date. BLACK, not grey (rule 1) —
   the step down is size and the date above it staying at full weight, which is what the rule
   says to use instead. nowrap because a time is one atom: "9:06" above a stranded "PM" reads
   as broken, the same way a split price does. */
.inv-doc .ip-clock{font-size:9.5pt;font-weight:700;color:#111827;white-space:nowrap;margin-top:1px;}
/* §112.722 — BLACK, not grey (rule 1, per Joel: "no gray text anywhere just have
   everything be black text"). This document inherited #6B7280/#4B5563 labels from the
   2026-08-01 PDF, which predates the locked no-grey rule and never got swept. Hierarchy
   here is carried by SIZE, WEIGHT and LETTER-SPACING -- 8pt/800/uppercase against 11pt
   body -- which is exactly what the rule says to use instead of greying text down. */
.inv-doc .ip-label{font-size:8pt;font-weight:800;letter-spacing:0.08em;text-transform:uppercase;
  color:#111827;margin-bottom:3px;}
.inv-doc .ip-strong{font-weight:700;}
/* §112.3411 — `separate`, NOT `collapse`, and that is forced rather than preferred: under
   border-collapse:collapse a browser IGNORES border-radius on cells outright, so the service box
   could only ever be square. Per Joel: "we have rounded edges, no square boxes."
   border-spacing:0 keeps every row exactly where collapse put it, so nothing else on this document
   moves — only the corners can now round. */
.inv-doc table{width:100%;border-collapse:separate;border-spacing:0;margin-bottom:10px;}
.inv-doc thead th{font-size:8pt;font-weight:800;letter-spacing:0.06em;text-transform:uppercase;
  color:#111827;text-align:left;padding:0 8px 5px;border-bottom:1.5px solid #D1D5DB;}
/* nowrap on the MONEY cells only (§112.542). Caught by rendering the document at 390px:
   "−$75.00" broke across two lines, leaving a bare "−" stranded above "$75.00" -- which on
   a customer's estimate reads as a different number entirely. A price is one atom and must
   never break, and this is the sanctioned use of nowrap (rule 10's concern is a wide
   element forcing the PAGE sideways; one short money cell cannot). Qty is number-only
   since §112.718 (Joel circled the unit word: "remove the job"), so it cannot go wide. */
.inv-doc thead th.num,.inv-doc tbody td.num{text-align:right;font-variant-numeric:tabular-nums;}
.inv-doc tbody td.num:nth-child(n+3),.inv-doc thead th.num:nth-child(n+3){white-space:nowrap;}
/* ⛔ §112.6386 — 6px SIDE PADDING ON THE MONEY CELLS, NOT 8. Per Joel: "move the other price over
   to the left more ... so they don't stack." Four columns at 8px a side spend 64px of a 322px
   table on padding alone, and the two that cannot wrap are the two that were overflowing. The 4px
   this returns is what lets AMOUNT hold "$12,500.00" (92.1px needed, 93.4px given) instead of
   clipping into its neighbour. Vertical padding is untouched -- row height is not the problem. */
.inv-doc tbody td.num:nth-child(n+3),.inv-doc thead th.num:nth-child(n+3){padding-left:6px;padding-right:6px;}
.inv-doc tbody td{padding:6px 8px;border-bottom:1px solid #E5E7EB;vertical-align:top;}
.inv-doc tbody tr{break-inside:avoid;}
.inv-doc .ip-li-desc{font-weight:600;}
/* §112.3402 — an itemized service's DETAIL, indented under the service it belongs to. Per Joel:
   "those aren't services, that is inventory and parts and materials INSIDE of the service."
   Indent plus a lighter weight, and nothing else: this is the sheet a customer prints or is
   handed as a PDF, so it stays black on white with no accent colour to burn ink or drop out on a
   mono printer. The service row above keeps font-weight:600 and carries the total, so the
   hierarchy reads at a glance without a rule or a fill. */
/* §112.3405 — NO INDENT. Per Joel: "everything should always be full width." The indent was
   saying what the weight above it already says — the service row is 600, its detail is 400 — and
   it cost every detail line 14px of a page that has to hold a wrapping service name. */
.inv-doc .ip-li-sub{font-weight:400;}
/* ⛔ §112.3412 — ONE BOX PER SERVICE: THE NAME ON THE BORDER, THE COLUMNS INSIDE.
   Per Joel: "put the service on top of the border of the service and put the qty/hr rate amount
   inside the box", then "description and other details go inside the box, the service name sits on
   top the box."
   ⚠ THIS REPLACED A ROW-CLASS BOX drawn with borders on <td>s. That shape could never do what he
   asked: a row inside a <table> cannot straddle its own container's border. So the box is a real
   div, the name is absolutely positioned over its top edge, and each service carries its OWN small
   table with its own headers.
   THE COLUMNS STILL ALIGN ACROSS BOXES because every table is table-layout:fixed over one shared
   <colgroup> — without it each table would size columns to its own content and two services would
   print two different grids.
   BLACK, not orange, on this surface and the internal one alike (§112.3411): the sheet is printed,
   so orange is ink the customer pays for that drops out on a mono printer. */
/* ⛔ §112.3817 — THE SERVICE NAME IS ABOVE THE BOX. IT IS NOT ON THE BORDER AND IT IS NOT IN IT.
   Per Joel, on the customer copy: "why is the service not sitting on top of the details… how is
   this happening again? What did you do to break it?"
   ⚠ NOTHING BROKE, AND SAYING SO MATTERS MORE THAN THE FIX. Read from the history rather than
   guessed: exactly ONE commit ever created this element (a3a4421, 08-22 07:14, titled "The service
   sits on the box border"), and the two that followed it -- 8c14df6 "The service name had a border
   running through it" and 1ddbc32 "The service name gets real air above it" -- were both
   REFINEMENTS of the on-the-border design, not attempts to leave it. The five hours he remembers
   were §112.3431, on the INTERNAL view in full-flow.html, which already says in its own comment
   "It goes above the border, not on the border". Two renderers, one of them fixed. This is the
   other one.
   ⚠ AND §112.3416 BELOW IT WAS TRUE AND IRRELEVANT. It measured the legend's overhang and sized
   the margin to `margin - 9`, chasing white space above a name whose real problem was that a label
   painting over a 2px border cannot leave that border whole -- the same eight-attempts-at-spacing
   trap §112.3431 records one screen over. THE SHAPE WAS THE BUG, NEVER THE GAP. Its arithmetic is
   deleted with the element it measured.
   PLAIN SIBLINGS, mirroring .inv-svc-line / .inv-li-group exactly: no wrapper, no positioning off
   each other, no negative offsets. Either can move without the other. */
.inv-doc .ip-svc-line{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  padding:0 4px;margin:18px 0 6px;}
.inv-doc .ip-svc-line-name{font-weight:700;font-size:10.5pt;color:#111827;}
.inv-doc .ip-svc-line-amt{font-weight:700;font-size:10.5pt;color:#111827;white-space:nowrap;}
/* The box is a closed rectangle and nothing paints over it. `position:relative` went with the
   legend -- there is nothing left to position against it. */
.inv-doc .ip-svc-box{border:2px solid #111827;border-radius:12px;padding:8px 8px 4px;margin:0 0 10px;}
/* table-layout:fixed is load-bearing — see the colgroup note above. */
.inv-doc table.ip-tbl{width:100%;table-layout:fixed;border-collapse:collapse;margin:0;}
.inv-doc table.ip-tbl-loose{margin-bottom:10px;}
/* ⛔ §112.6388 — DO NOT ADD AN OUTER-COLUMN PADDING RULE HERE. IT ALREADY EXISTS, at the bottom of
   this file: `.inv-doc thead th:first-child` / `td:last-child` already zero exactly this, and the
   8px that remains is the CARD's padding, which Joel asked for by name after a pass took it to
   zero and the text ran into the orange border ("you have to have a little bit of buffer around
   them"). I re-derived this from the base `td{padding:6px 8px}` rule without reading to the end of
   the file, told Joel there was ~14px to reclaim, and wrote a duplicate that changed nothing.
   MEASURED, both builds: `td:first-child` padding-left is 0px on production and 0px after. This
   line is the receipt so the next session does not spend the same round on it. */
/* ⛔ §112.3818 — A HEADER SITS OVER ITS OWN NUMBERS. Per Joel, circling QTY/HR and RATE on the
   customer invoice: "move the quantity and the hour over to the left, and the numbers behind it
   too — so Qty/Hr is centered right in the middle of those numbers. And then the rate just gets
   centered; you can leave the details below the same."
   ⚠ WHY IT LOOKED WRONG, AND §112.3409 IS NOT UNDONE, IT IS SUPERSEDED. That section right-aligned
   QTY/HR and padded it 22px to stop it crowding RATE — correct for the problem it had. But
   right-aligning a SIX-character header over a ONE-character value pins their right edges together
   and throws the header's whole body out to the left of the number, which is the gap he circled.
   A wide label over a narrow value can only line up on its CENTRE.
   TWO COLUMNS, TWO TREATMENTS, and the difference is his:
     QTY/HR — header AND values both centre. He asked for the numbers to move too, and one column
              centred on itself needs no padding to keep it off RATE, so the 22px goes.
     RATE   — the HEADER centres; the values stay right-aligned ("leave the details below at the
              same"). Money right-aligns down the page so the decimal points stack, and that is not
              this change's business. */
.inv-doc thead th:nth-child(2), .inv-doc tbody td:nth-child(2){text-align:center;padding-right:8px;}
.inv-doc thead th:nth-child(3){text-align:center;}
/* §112.722 — this line no longer prints the TYPE word ("Service" under a service, per
   Joel: "it doesn't need to be anywhere, people know that that's the service"). It renders
   ONLY for a non-taxable item, which is a real tax fact on the customer's copy and the one
   thing he asked to keep. */
.inv-doc .ip-li-type{font-size:8.5pt;color:#111827;margin-top:2px;}
/* Full width — "subtotal should be moved all the way over left aligned not floating". */
.inv-doc .ip-totals{width:100%;break-inside:avoid;}
.inv-doc .ip-trow{display:flex;justify-content:space-between;padding:3px 0;font-variant-numeric:tabular-nums;}
.inv-doc .ip-trow.grand{border-top:2px solid #111827;margin-top:4px;padding-top:6px;
  font-size:13pt;font-weight:800;}
.inv-doc .ip-trow.credit{color:#047857;}
/* The five-part Service Summary (What We Found / Plan of Action / Work Completed /
   Verified-Tested / Recommendation) at the BOTTOM of the document, orange labels, a line
   between entries. break-inside:avoid per entry: a print page break may land BETWEEN
   entries, never through one. Never renders on an estimate (§112.536). */
.inv-doc .ip-summary{margin-top:12px;padding-top:10px;border-top:1px solid #E5E7EB;}
.inv-doc .ip-sum-head{font-size:9pt;font-weight:800;letter-spacing:0.08em;text-transform:uppercase;
  color:#111827;margin-bottom:2px;}
.inv-doc .ip-sum-item{padding:7px 0;border-bottom:1px solid #E5E7EB;break-inside:avoid;}
.inv-doc .ip-sum-item:last-child{border-bottom:none;}
.inv-doc .ip-sum-item .ip-label{color:var(--orange);margin-bottom:2px;}
.inv-doc .ip-sum-text{font-size:9.5pt;}
.inv-doc .ip-foot{margin-top:12px;padding-top:10px;border-top:1px solid #E5E7EB;
  font-size:9.5pt;color:#111827;break-inside:avoid;}
.inv-doc .ip-foot-terms{font-weight:700;color:#111827;margin-bottom:4px;}
/* Screen wrapper: the document as a white card on the black page (Customer View). The
   print node needs no padding — @page margins are the paper's own. */
.inv-doc-card{background:#fff;border:2px solid var(--orange);border-radius:16px;overflow:hidden;margin-bottom:14px;}
/* §112.3406 — THE DOCUMENT USES THE WHOLE CARD. Per Joel, circling the line-item table: "you're
   having wasted space in the area that I circled when it should be full width as well."
   MEASURED IN THE REAL APP, not guessed: the card gives 374px and the table was using 342 — 14px
   of horizontal padding here plus 8px of cell padding on the outer columns, 22px a side. The
   vertical 12px stays; it is what separates the document from the card's own edge.
   ⚠ SCREEN ONLY. The print node deliberately carries no padding at all (see the note above) —
   @page margins are the paper's own — so this cannot narrow a printed invoice. */
.inv-doc-card .inv-doc, .approval-dev-scroll .inv-doc{padding:12px 8px;}
/* The outer columns give their edge padding back — 8px on the first cell and 8px on the last were
   insetting the table inside its own container, on top of the card's own padding. Inner gutters
   keep theirs so the columns still read as columns.
   ⚠ THE CARD KEEPS 8px AND THAT IS NOT WASTE. A first pass took both to zero and the black text
   ran straight into the orange border — per Joel: "you have to have a little bit of buffer around
   them." FULL WIDTH MEANS USE THE SPACE, NOT REMOVE THE BREATHING ROOM. 8px is the buffer; the
   22px-a-side inset it replaced was the waste. */
.inv-doc thead th:first-child, .inv-doc tbody td:first-child{padding-left:0;}
.inv-doc thead th:last-child,  .inv-doc tbody td:last-child{padding-right:0;}
.approval-dev-scroll .inv-doc{padding:2px 0 6px;}

/* §112.1105 — the slot that carries the change-order PHOTOS up next to What We Found on Update
   Invoice & Get Approval. Purely a gap: MEASURED at 366px, the photo tile's bottom and the
   line-item table's top were both at 466px, so the tile was welded to the header row. The photos
   block has no bottom margin of its own because in its usual position it is the last thing on
   screen with nothing to sit above. Renders nowhere else — nothing sets summaryExtraHtml but
   scPreviewInvoice(), so the printed sheet and every other surface are untouched. */
.ip-sum-extra{margin-bottom:14px;}

/* ⛔ §112.1601 — THE BASE PHOTO RULES, BECAUSE THIS STYLESHEET ONLY REFERENCED THEM.
   The .ip-sum-extra override below has named .inv-cust-photo since §112.1105 while the rule it
   overrides lived in full-flow.html — and approve-invoice.html does not load full-flow.html. So on
   the page the customer actually opens, .inv-cust-photo had NO styling at all. That is CLAUDE.md's
   named bug class verbatim: "a component's override exists but the BASE rule it depends on was
   never copied over — the override alone does nothing visible."
   ⚠ LITERAL #0A0A0A, NOT var(--ink): this stylesheet defines no custom properties and the page
   that loads it defines none either, so `var(--ink)` with no fallback is an INVALID value here —
   the border would silently fall back to currentColor. Measured from full-flow.html:224. */
.inv-cust-photos{display:flex;gap:8px;flex-wrap:wrap;}
/* ⚠ box-sizing IS NOT OPTIONAL HERE. full-flow.html sets border-box globally; this stylesheet
   does not, and the page that loads it does not either — so the identical rule measured 88px
   in-app and 92px on the customer's page. Same component, two sizes, on the two surfaces that
   are supposed to be the same document. Stated on the rule rather than globally, so this file
   cannot change the box model of a host page it does not own. */
.inv-cust-photo{box-sizing:border-box;width:88px;height:88px;border-radius:12px;border:2px solid var(--ink,#0A0A0A);
  background:#F1F1F3;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:4px;color:var(--ink,#0A0A0A);position:relative;overflow:hidden;}
.inv-cust-photo svg{width:26px;height:26px;}
.inv-cust-photo span{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.03em;
  position:relative;z-index:1;}
/* Real photo behind the label — white text + shadow so it stays readable over any photo. */
.inv-cust-photo.has-photo span{color:#fff;text-shadow:0 1px 4px rgba(0,0,0,.9);}
.inv-cust-photo .photo-real-fill{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
.ip-photos{margin-top:14px;}

/* §112.1105 — the icon and its word sit higher in the tile, per Joel: "move photo icon and text
   up a little". MEASURED: .inv-cust-photo is a fixed 88px box with justify-content:center, and
   the content (26px icon + 4px gap + 12px word = 42px) was dead-centre — 23px of nothing above
   it and 23px below. Top-aligned with 15px now, so it lifts 8px and the slack collects at the
   bottom where the tile already has a border to close it.
   SCOPED TO .ip-sum-extra, which only this screen renders. .inv-cust-photo is shared by the
   invoice view and the printed document's own photo strip, and those are centred deliberately —
   changing the class itself would move a tile Joel has not asked about on four other surfaces. */
.ip-sum-extra .inv-cust-photo{justify-content:flex-start;padding-top:15px;}

/* ⛔ THE PHOTO STRIP AND THE FOOTER SHARE ONE ROW — 2026-09-02. See buildCustomerDocHtml's own
   note for why: it is a page-count fix. The footer used to sit under everything and pushed a
   two-line block onto a second sheet, so printing 46 invoices produced 61 pages.
   The photos take the width they need; the footer takes the dead width beside them and drops
   below only when the strip genuinely fills the line. `align-items:flex-start` so neither is
   positioned by the other — this file's own rule about a part held in place by its neighbour. */
.inv-doc .ip-tail{display:flex;align-items:flex-start;justify-content:space-between;
  gap:16px;flex-wrap:wrap;break-inside:avoid;}
.inv-doc .ip-tail .ip-photos{flex:0 1 auto;margin-top:14px;}
/* The border-top went with the move: it drew a full-width rule under the document, and beside a
   photo strip it would cut the row in half. The footer keeps its own top margin so it still sits
   clear of whatever is above it. */
.inv-doc .ip-tail .ip-foot{flex:1 1 200px;margin-top:14px;padding-top:0;border-top:none;
  text-align:right;}
/* Nothing to sit beside: with no photos the footer is the whole row and takes the full width,
   which is exactly where it was before this change. */
.inv-doc .ip-tail-alone .ip-foot{text-align:left;}
