/*
 * SINGLE SOURCE OF TRUTH stylesheet.
 * This exact file is copied into:
 *   FE/src/styles/print.css   (used by the live React preview, with paged.js)
 *   BE/src/main/resources/static/print.css (used by the Java-rendered final HTML, with paged.js)
 *
 * If you edit this file, copy it to BOTH locations. Do not fork it -- the whole
 * point of one shared stylesheet is that styling can never drift between the
 * preview and the printed PDF. See shared/schema.md for the class-naming contract
 * that both renderers (FE TSX and BE Java) must follow.
 */

:root {
  --page-width: 8.5in;
  --page-height: 11in;
  --margin-top: 20pt;
  --margin-bottom: 55pt; /* reserves room for the footer, matches the old PDFKit DOC_END.Y convention */
  --margin-left: 20pt;
  --margin-right: 20pt;
  --font-color: #000;
  --border-color: #000;
  --header-bg: #dee0e3;
  --row-alt-bg: #f2f2f2;
  --font-family: "Poppins", "Helvetica", "Arial", sans-serif;
}

/* Poppins -- the exact family the old Node/PDFKit invoice used (registered in
   its pdf-generator.js). Vendored the same TTFs so weights map 1:1:
   400 Regular, 500 Medium (Node's value font), 600 SemiBold (Node's label/
   header font), 700 Bold. Served from /fonts/ by both FE (public/fonts) and
   BE (static/fonts), same as Code39. */
@font-face {
  font-family: "Poppins";
  font-weight: 200;
  src: url("/fonts/Poppins-ExtraLight.ttf") format("truetype");
}
@font-face {
  font-family: "Poppins";
  font-weight: 400;
  src: url("/fonts/Poppins-Regular.ttf") format("truetype");
}
@font-face {
  font-family: "Poppins";
  font-weight: 500;
  src: url("/fonts/Poppins-Medium.ttf") format("truetype");
}
@font-face {
  font-family: "Poppins";
  font-weight: 600;
  src: url("/fonts/Poppins-SemiBold.ttf") format("truetype");
}
@font-face {
  font-family: "Poppins";
  font-weight: 700;
  src: url("/fonts/Poppins-Bold.ttf") format("truetype");
}

/* Barcode-style rendering for Field.isBarcode -- same trick the old Node/PDFKit
   invoice used: a Code39 TTF renders `*value*` as a scannable-looking barcode
   glyph run. Not a real generated barcode image, just a font. Served from
   /fonts/Code39.ttf by both FE (public/fonts) and BE (static/fonts). */
@font-face {
  font-family: "Code39";
  src: url("/fonts/Code39.ttf") format("truetype");
}

* {
  box-sizing: border-box;
}

/* Don't let a scrollbar reserve layout width. In the Playwright/Chromium PDF
   render (and the preview iframe) the paged root can reserve ~7pt for a
   vertical scrollbar; Chromium then subtracts that from every `width:100%`
   element, so tables/banners come out ~7pt narrow. The negative-margin left
   edge is unaffected, so the shortfall lands entirely on the RIGHT -- an
   uneven right margin (or, with full-bleed, a strip short of the paper edge).
   Removing the reserved scrollbar makes `100%` the true content width again,
   so tables reach the right margin and both sides are an even 20pt. This
   file is only loaded inside the render/preview document, never the builder
   app shell, so it can't affect the editor's own scrollbars. */
html,
body {
  scrollbar-width: none; /* Firefox + modern Chromium */
}
::-webkit-scrollbar {
  width: 0 !important;
  height: 0 !important;
  display: none; /* Chromium/WebKit */
}

/* Font is declared on the paged render roots (.pagedjs_pages after the
   polyfill runs, .doc for the pre-paged source), NOT only on body: in the
   builder preview the pages live inside the app UI, whose own font-family on
   a closer ancestor (.builder-app) would otherwise win over an inherited body
   rule -- silently diverging the preview from the final PDF (which has no app
   CSS). A rule directly on these roots beats inheritance in both. */
body,
.doc,
.pagedjs_pages {
  margin: 0;
  font-family: var(--font-family);
  color: var(--font-color);
  font-size: 8pt;
  /* Same fix as .table-component table: Poppins' tall line-height metrics
     (~1.5-1.6x) otherwise inflate every field/label/value line well beyond
     what its font-size + our explicit padding/gap would suggest, throwing
     off the whole document's vertical rhythm vs the old PDFKit invoice
     (which has no font-metric-driven leading at all). */
  line-height: 1.2;
}

/* ---------------- Page setup (CSS Paged Media, read by Paged.js) ---------------- */

/* Layout only (tenant-independent). The actual footer TEXT (powered-by string,
   page-number format) is template-specific data, so it is generated as a small
   inline <style> block per render (see BE HtmlRenderService / FE PageRenderer)
   rather than baked into this shared file -- @page margin boxes don't reliably
   read attr() off body/document elements the way a normal element box would. */
@page {
  size: var(--page-width) var(--page-height);
  margin: var(--margin-top) var(--margin-right) var(--margin-bottom) var(--margin-left);
}

@page {
  @bottom-left {
    font-size: 9pt;
    color: var(--font-color);
  }

  @bottom-right {
    font-size: 9pt;
    color: var(--font-color);
  }
}

/* ---------------- Structured flow: page / section / row / column ---------------- */

.doc {
  width: 100%;
  /* Containing block for any top-level Section that is itself anchored
     (position:absolute, free-moved/resized on the builder canvas). */
  position: relative;
}

/* Builder-only guides: FE adds .section-guide on each section/header box when
   "Section outlines" is on. Class is on the element itself (not only under .doc)
   so continuation headers still show dashes after paged.js pulls them into
   running margin boxes. Final PDF never sets .section-guide. */
.section-guide {
  border: 1px dashed #2563eb !important;
  box-sizing: border-box;
}

/* Each component block (e.g. 10-field text + 3-field text in a header) */
.block-guide {
  border: 1px dashed #ea580c !important;
  box-sizing: border-box;
}

.doc.show-section-guides .col {
  outline: none;
}

/* A Section never sets a height by default (sizeMode: auto) -- it is always
   exactly as tall as its rows. sizeMode: fixed adds the .fixed class below,
   giving it an explicit height (inline style) and clipping any overflow --
   "all components stay inside the section, not outside it." */
.section {
  width: 100%;
  /* Containing block for any anchored component inside this section -- this is
     what makes "move freely, but never outside the parent section/header"
     work: the component's position:absolute is relative to THIS box. */
  position: relative;
}

.section.fixed {
  overflow: hidden;
}

/* Section.verticalAlign: only matters once the box is taller than its rows
   need (sizeMode:"fixed" with room to spare) -- flex column + justify-content
   distributes that extra space above/around the rows instead of leaving it
   stuck below them. Plain block flow (the default, no class) already IS
   top-aligned, so "top" needs no rule of its own. */
.section.valign-center {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.section.valign-bottom {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

/* Section.fullBleed: paints the section's background band edge-to-edge across
   the paper (through the page margins), keeping its content at the margin
   line -- the old Node invoice's dark "Previous/Total Due Balance" banner is
   drawn this way (rect from x=0 to full page width). */
.section.full-bleed {
  width: calc(100% + var(--margin-left) + var(--margin-right));
  margin-left: calc(-1 * var(--margin-left));
  padding-left: var(--margin-left);
  padding-right: var(--margin-right);
}

/* Anchored section: free-positioned within its own parent (.doc, or the
   first-page header slot) instead of stacking in document flow. Mirrors
   .component.anchored. NOTE: .header-continuation-source is deliberately
   excluded -- it already uses position:running() (see header variants below)
   to repeat itself in the @top-center margin box on every page 2+; anchoring
   doesn't compose with that, so the builder never offers it there. */
.section.anchored,
.header-first-page.anchored {
  position: absolute;
}

/* Default gap BELOW each section, overridable per section via marginBottomPt
   (inline style wins). Deliberately margin-bottom on the section itself, not
   a `.section + .section { margin-top }` sibling rule: with the sibling rule a
   section could never opt into a 0pt gap (the old invoice butts the dark
   due-balance banner directly against the store-credit line above it). */
.section {
  margin-bottom: 8pt;
}

/* A Row lays columns out left-to-right on the SAME Y axis.
   nowrap keeps side-by-side X placements (x=20 and x=350) from wrapping
   onto a second line. */
.row {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: flex-start;
  width: 100%;
  gap: 0;
}

/* Single-column row: block flow so paged.js can paginate long tables/lists.
   Flex rows cannot break across pages -- overflow rows are silently dropped. */
.row.row-single {
  display: block !important;
}

.row.row-single > .col {
  display: block !important;
  /* Width comes from inline columnBoxStyles (100% for tables, fixed pt for
     offset columns like totals). Forcing 100% here broke right-shifted blocks. */
}

.table-component {
  display: block !important;
}

.table-component table,
.table-component tbody {
  break-inside: auto;
}

.col {
  min-width: 0;
  flex-shrink: 0;
}

/* ---------------- Component: sizing modes ---------------- */

/* auto (default): no height set, grows/shrinks with real content -- this is what
   makes the "3 active fields vs 10 active fields" tenant example work with zero
   extra code: fewer <div class="field"> children simply means a shorter box. */
.component {
  width: 100%;
}

.component.fixed {
  overflow: hidden;
}

.component.anchored {
  position: absolute;
}

/* ---------------- component: text (image + label/value fields) ---------------- */

.text-component {
  display: flex;
  flex-direction: column; /* image above text so text left edge matches Block X */
  gap: 4pt;
  align-items: flex-start;
}

.text-component .field-list {
  display: flex;
  flex-direction: column;
  gap: 1pt;
  flex: 1 1 auto;
  /* Fill the column: the parent .text-component uses align-items:flex-start
     (for the optional image), which would otherwise shrink this list to its
     text width -- breaking right-aligned fields and the field-split value
     column, which must reach the column's right edge (the Node totals'
     value/divider land at the far right). */
  align-self: stretch;
}

/* Block, not flex: Field.align maps to plain text-align (set inline by the
   renderer), which is what moves the TEXT inside the field's box -- the box
   itself (full column width, or a fixed-width chip) does not move. */
.field {
  display: block;
  white-space: pre-wrap;
  word-break: break-word;
}

.field .field-label {
  font-weight: 600;
  margin-right: 3pt;
}

/* Field.valueWidthPt: two-column "label | value" row, the exact geometry of
   the old Node totals section -- label fills the left side (right-aligned via
   Field.align), value is a fixed-width column pinned to the field's right
   edge, always right-aligned. Pair with dividerAlign:"right" +
   dividerWidthPt equal to the value column for the rule under just the value. */
.field.field-split {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  /* Guaranteed minimum label/value gap regardless of label text length --
     without this, a short value (e.g. "6") right-aligned in its own fixed
     column can end up nowhere near its label, while a long label (e.g. "Line
     Item (D/C)") could butt directly against the value column with none. */
  gap: 6pt;
}

.field.field-split .field-label {
  flex: 1 1 auto;
  margin-right: 0;
}

.field.field-split .field-value {
  flex: 0 0 auto;
  text-align: right;
}

/* Wrapper around each field + its optional divider. Stretch (not start) so a
   full-width field can use text-align, while align-self on the chip/divider
   handles their own placement. */
.field-wrap {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* Field.sameLine: consecutive fields joined on one line (PDFKit's
   `continued: true`) -- e.g. the page-2 header's barcode + " : 157194".
   PDFKit continues glyphs on the same baseline with only the natural advance
   (plus spaces already in the " : n" string) -- no flex gap, no label margin,
   and no wrap. */
.field-line {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  gap: 0;
  flex-wrap: nowrap;
  max-width: 100%;
}

/* Shrink-wrap each same-line field to its glyphs (block fields in a flex row
   otherwise stretch and leave odd gaps). Keep on one line like PDFKit. */
.field-line > .field {
  display: inline-block;
  white-space: nowrap;
  word-break: normal;
  width: auto;
  max-width: none;
  flex: 0 0 auto;
}

/* Label spacing for normal stacked fields is margin-right:3pt; on a same-line
   run the separator (e.g. " : ") already carries its own spaces -- extra margin
   pushed ": 212" away from the barcode vs the old PDF. */
.field-line > .field .field-label {
  margin-right: 0;
}

.field-line > .field .field-value.barcode {
  /* Stay in the flex baseline run; never stretch full column width. */
  display: inline-block;
  vertical-align: baseline;
}

/* Field.backgroundColor: a highlighted "chip" behind just this field (e.g. the
   old system's gray box behind "SOLD TO:"). The old Node invoice draws a
   FIXED-width rect (50pt) whose left edge is flush with the text below it --
   so: no left padding (text flush with the chip edge and the body lines), an
   explicit width from Field.backgroundWidthPt (inline style; fit-content when
   unset), and the chip itself always stays put (align-self) -- Field.align
   moves the text INSIDE it, not the chip. */
.field.field-highlight {
  align-self: flex-start;
  width: fit-content;
  padding: 2pt 0;
}

/* Field.dividerBelow: same look as a standalone divider component, just
   emitted inline between two fields of the same text component.
   Field.dividerWidthPt sets an explicit width (inline style from the
   renderer); Field.dividerAlign slides that shorter rule left/center/right --
   the old Node totals section draws exactly this: a ~100pt rule under just
   the right-aligned VALUE of each totals row, not under the whole line. */
.field-divider {
  border: none;
  border-top-width: 0.5pt;
  border-top-style: solid;
  border-top-color: var(--border-color);
  /* Tight: PDFKit rules the line right under the row (16pt row pitch at 10pt
     text); the line box's own leading already provides the visual gap. */
  margin: 0 0 1pt;
  width: 100%;
  align-self: stretch;
}

.field-divider.divider-left {
  align-self: flex-start;
}

.field-divider.divider-center {
  align-self: center;
}

.field-divider.divider-right {
  align-self: flex-end;
}

/* Field.isBarcode: value is pre-wrapped in asterisks by the renderer (BE
   TextComponentRenderer / FE TextComponentView), this just switches the font. */
.field .field-value.barcode {
  font-family: "Code39", monospace;
  /* No letter-spacing: the old Node/PDFKit system renders the Code39 run with
     the font's natural spacing; extra tracking makes the bars visibly wider
     than the reference (and hurts scannability). */
  font-weight: 400;
  /* PDFKit draws the barcode line with NO extra leading -- the browser's
     default line-height (~1.5 for Poppins metrics) opened a visible gap
     between "INVOICE : n" / barcode / "Date: ..." that the old PDF does not
     have. line-height:1 collapses it to the glyph height. inline-block (not
     block) keeps the glyph run sized to its content so page-2 sameLine
     barcode + " : n" sits correctly, and avoids a full-column-width box. */
  display: inline-block;
  line-height: 1;
  white-space: nowrap;
}

/* TableColumn.isBarcode: human-readable value on top, Code39 barcode under it
   (old invoice UPC column stack). inline-flex so td text-align still positions
   the whole stack left/center/right; row height grows naturally. */
.cell-barcode-stack {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1pt;
  max-width: 100%;
  vertical-align: top;
  line-height: 1.15;
}

.cell-barcode-stack .cell-text {
  display: block;
  word-break: break-word;
}

.cell-barcode-stack .field-value.barcode {
  font-family: "Code39", monospace;
  font-weight: 400;
  font-size: 14pt;
  display: block;
  line-height: 1;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
}

/* ---------------- component: image ---------------- */

.image-component img {
  display: block;
  max-width: 100%;
  object-fit: contain;
}

/* ---------------- component: table ---------------- */

.table-component table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: 8pt;
  /* Poppins' own line-height metrics are unusually tall (~1.5-1.6x) --
     inherited by every th/td this inflated each row 2.5-4pt beyond the old
     PDFKit invoice's row height (PDFKit has no such concept; a "row" there is
     just wherever .text() left the cursor). Pin it down so OUR padding is the
     only thing controlling row height, matching the reference's compact rows. */
  line-height: 1.2;
}

.table-component thead {
  /* table-row-group: paged.js paginates tbody itself; table-header-group makes
     Chromium re-inject thead at print time and shifts page-break math so row 28
     lands in the page margin (missing from PDF). Continuation pages already
     omit thead in the paged.js split. */
  display: table-row-group;
}

/* Kept for clarity if thead appears inside a paginated page fragment. */
.pagedjs_page .table-component thead {
  display: table-row-group;
}

/* paged.js sets overflow:hidden on each sheet; table rows parked at the top
   of a continuation page get clipped when Chromium prints to PDF. */
.pagedjs_sheet {
  overflow: visible !important;
}

.pagedjs_pagebox > .pagedjs_area,
.pagedjs_pagebox > .pagedjs_area > .pagedjs_page_content {
  overflow: visible !important;
}

.table-component thead th {
  background: var(--header-bg);
  text-align: left;
  padding: 3pt 4pt;
  font-size: 10pt;
  font-weight: 600;
  border-top: 1pt solid var(--border-color);
  border-bottom: 1pt solid var(--border-color);
}

.table-component tbody td {
  padding: 3pt 4pt;
  vertical-align: top;
}

.table-component tbody tr {
  break-inside: avoid; /* a single row never splits across a page boundary */
}

.table-component tbody tr.alt {
  background: var(--row-alt-bg);
}

.table-component tbody tr.blank-row td {
  color: transparent; /* padding rows used by rowMode:"fixed" when data is shorter than fixedRowCount */
}

/* sortedByCategory: gray band header row inside the line-items table (Node parity). */
.table-component tbody tr.category-row td {
  background: var(--header-bg);
  font-weight: 600;
  border-top: 1pt solid var(--border-color);
  border-bottom: 1pt solid var(--border-color);
  break-inside: avoid;
}

.table-component tbody tr.category-row td.category-label {
  text-align: left;
}

.table-component img.line-item-thumb {
  display: block;
  max-width: 48pt;
  max-height: 48pt;
  object-fit: contain;
  margin: 0 auto;
}

.table-component.full-bleed tbody tr.category-row td.category-label,
table.full-bleed-table tbody tr.category-row td.category-label {
  padding-left: var(--margin-left);
}

/* TableComponent.showRowNumber: 1-based Sr/line number as a narrow first
   column. The old Node invoice draws the same counter (index + 1) beside each
   line item, gated by its hideLineItemCount config flag; the header cell stays
   empty there, so it stays empty here too. */
.table-component th.row-num,
.table-component td.row-num {
  width: 16pt;
}

.table-component col.row-num-col {
  width: 16pt;
}

/* TableComponent.fullBleed: edge-to-edge bands. Also on the <table> itself so
   paged.js page-2+ fragments (split without the wrapper div) keep the same width. */
.table-component.full-bleed table,
table.full-bleed-table {
  width: calc(100% + var(--margin-left) + var(--margin-right));
  margin-left: calc(-1 * var(--margin-left));
}

.table-component.full-bleed th:first-child,
.table-component.full-bleed td:first-child,
table.full-bleed-table th:first-child,
table.full-bleed-table td:first-child {
  padding-left: var(--margin-left);
}

.table-component.full-bleed th:last-child,
.table-component.full-bleed td:last-child,
table.full-bleed-table th:last-child,
table.full-bleed-table td:last-child {
  padding-right: var(--margin-right);
}

.table-component.full-bleed th.row-num,
.table-component.full-bleed td.row-num,
table.full-bleed-table th.row-num,
table.full-bleed-table td.row-num {
  padding-left: 6pt;
  padding-right: 0;
  width: var(--margin-left);
}

.table-component.full-bleed col.row-num-col,
table.full-bleed-table col.row-num-col {
  width: var(--margin-left);
}

/* ---------------- component: divider ---------------- */

.divider-component hr {
  border: none;
  border-top-width: 1pt;
  border-top-style: solid;
  border-top-color: var(--border-color);
  margin: 2pt 0;
}

/* ---------------- component: list (variable-length title+body blocks) ---------------- */

/* For store policy text etc: a variable number of {title, value} entries from
   the bound data array, stacked vertically -- Table's "repeat per data item"
   idea, but for stacked paragraph blocks instead of tabular rows.

   Deliberately plain BLOCK flow (not flexbox), with margins instead of `gap`:
   a long policy body (e.g. the Battery Purchase paragraph) frequently has to
   split across a page boundary, and paged.js CANNOT break inside a flex
   container -- it silently drops the overflowing part. The browser Previewer
   did exactly that (title stranded at the bottom of page 1, body gone),
   even though the Playwright polyfill happened to tolerate it. Block flow
   lets the paragraph break across pages the same way in both. */
.list-component {
  display: block;
}

.list-component .list-item {
  display: block;
  /* Keep each policy block (title + its paragraph) together as one unit.
     Each item is far shorter than a page, so the robust behavior when one
     doesn't fit at the bottom of a page is to move the WHOLE item to the
     next page -- not to split it. paged.js drops content when it tries to
     split an item across the boundary and can't; making the item atomic
     (and its containers breakable, so paged.js can break BETWEEN items)
     lets it relocate the item cleanly instead. */
  break-inside: avoid;
}

.list-component .list-item + .list-item {
  margin-top: 6pt;
}

.list-component .list-item-title {
  font-weight: 600;
  margin-bottom: 1pt;
}

.list-component .list-item-value {
  white-space: pre-wrap;
  word-break: break-word;
  /* NOT text-align: justify (even though the old Node code passes
     align:"justify"): with pre-wrap, CSS justification also stretches lines
     ending in a preserved \n, blowing huge word gaps into the short policy
     lines. Left-aligned reads identical for this content. */
}

/* ---------------- header variants ---------------- */

.header-first-page {
  /* Default gap; override via section.marginBottomPt in the builder */
  margin-bottom: 8pt;
}

/*
 * Continuation header: rendered once, in flow, then paged.js's native "running
 * element" support (verified against node_modules/pagedjs/src/modules/generated-
 * content/running-headers.js -- this is a real, spec-compliant CSS Paged Media
 * feature, not a hand-rolled hack) pulls it out of the flow (position: running)
 * and replays it into the @top-center margin box of every page. The @page :first
 * override below then suppresses it specifically on page 1, where the full
 * header (rendered separately, in normal flow -- see .header-first-page) already
 * appears once and does not repeat.
 */
.header-continuation-source {
  position: running(continuationHeader);
  font-size: 8pt;
  font-weight: 600;
  /* The @top-center margin box text-align defaults to center (CSS Paged
     Media spec / pagedjs base css) and that inherits into our content --
     the old invoice's page-2 header is left-aligned at x=20. */
  text-align: left;
}

@page {
  @top-center {
    content: element(continuationHeader);
  }
}

@page :first {
  @top-center {
    content: none;
  }
}

/*
 * Margin boxes default to vertical-align:middle (pagedjs base.js:
 * .pagedjs_margin-top-center { align-items: center }) -- when the header's
 * real content (esp. a barcode field, which renders tall) is taller than
 * the reserved band, centering makes it overflow EQUALLY above and below,
 * bleeding it up past the physical page edge. Top-aligning means any
 * leftover/shortfall shows up only below the header (safe) instead of above
 * it (clipped/off-page).
 *
 * Neither the spec-correct CSS for this (`@page { @top-center { vertical-
 * align: top; } }`, which pagedjs's atpage.js does translate to align-items)
 * nor a plain `.pagedjs_margin-top-center { align-items: flex-start
 * !important; }` override survives pagedjs's own CSS processing pipeline --
 * verified by inspecting document.styleSheets after render: neither rule
 * makes it into any of pagedjs's regenerated <style> blocks, so the fix has
 * to run as JS after pagedjs finishes rendering instead. Applied identically
 * in both places the polyfill runs: BE's pagedjs-continuation-header.js
 * (final PDF, via Playwright) and FE's PagedPreview.tsx (live preview) --
 * see both for the actual `align-items: flex-start` fixup.
 */

/* ---------------- watermark (anchored, page-centered) ---------------- */

.watermark {
  position: absolute;
  top: 45%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-45deg);
  font-size: 60pt;
  font-weight: 700;
  color: red;
  opacity: 0.2;
  pointer-events: none;
  white-space: nowrap;
}
