:root {
  color-scheme: light;
  --border: #ccc;
  --panel-bg: #fafafa;
  --danger: #b3261e;
  --primary: #1a4d8f;
  --success: #1a7a3d;
}

* {
  box-sizing: border-box;
}

body {
  font-family: system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  margin: 0;
  padding: 0;
  color: #1a1a1a;
  line-height: 1.4;
  background: #e9e9e9;
}

/* --- Toolbar --- */

.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1.5rem;
  background: #fff;
  border-bottom: 1px solid var(--border);
}

.toolbar-title {
  font-weight: 700;
  font-size: 1.1rem;
}

.toolbar-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.inline-form {
  display: inline-flex;
}

.notice {
  margin: 0;
  padding: 0.6rem 1.5rem;
}

.notice.error-banner {
  color: var(--danger);
  background: #fdecea;
  border-bottom: 1px solid var(--danger);
}

.notice.success-banner {
  color: var(--success);
  background: #eaf7ef;
  border-bottom: 1px solid var(--success);
}

button,
.button-like {
  cursor: pointer;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.45rem 0.9rem;
  font-size: 0.9rem;
  background: #fff;
  color: #1a1a1a;
  text-decoration: none;
  display: inline-block;
}

button.primary,
.button-like.primary {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

button.danger {
  background: #fff;
  border-color: var(--danger);
  color: var(--danger);
}

/* --- A4 invoice sheet --- */

.invoice-page {
  width: 210mm;
  min-height: 297mm;
  margin: 1.5rem auto;
  /* 20mm margin on top/left/right, matching .print-page below exactly
     - the on-screen editor and the printed page share the same page
       geometry by design. Owner requirement: the gap under the footer is
       about a third smaller, so the bottom margin is 2/3 of 20mm. */
  padding: 20mm;
  padding-bottom: calc(20mm * 2 / 3);
  background: #fff;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
  /* Column layout so the footer (margin-top: auto below) sits flush at
     the bottom of the page for short invoices instead of rising directly
     under the last item. This is the on-screen editor only; printing
     uses a separate, JS-built #print-document (see below and app.js). */
  display: flex;
  flex-direction: column;
}

/* --- Stage 2: real per-page A4 boxes for the screen preview --- */

/* Built by app.js (renderScreenPageBoxes) only for multi-page invoices -
   see the function itself for why. A single-page invoice is left as the
   untouched .invoice-page (already exactly one correctly sized A4 sheet),
   so none of this ever exists in the DOM for that (most common) case. */
.a4-pages {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2rem;
  margin: 1.5rem auto;
}

/* Same physical page geometry as .print-page below (210x297mm, 20mm
   padding) - screen and print intentionally share one page box definition
   in spirit. No overflow: hidden, deliberately: buildPrintDocument()
   already verified this exact content fits one physical page's content
   area, so nothing should ever spill - and if a screen/print rendering
   discrepancy ever made it spill, it must stay visible (and is caught by
   the real-Chrome tests), never be silently clipped. */
.a4-page {
  width: 210mm;
  /* A hard physical height, not min-height: a page box can never grow past
     A4. Pagination is decided in print geometry (app.js
     withPrintGeometry) and every block consumes the same height on screen,
     so real content always fits; screen-only extras either sit outside the
     page (the add control) or take no layout space (the placeholders
     below). */
  height: 297mm;
  box-sizing: border-box;
  padding: 20mm;
  padding-bottom: calc(20mm * 2 / 3);
  background: #fff;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
  display: flex;
  flex-direction: column;
}

.a4-page-content {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* ".a4-page-last" (app.js renderScreenPageBoxes), not ":last-child":
   confirmed regression - the "+ Position hinzufügen" host is appended to
   .a4-pages right after this same page whenever it holds the last item
   fragment (the common case where the item table and the trailer share
   one page), making that host - not this page - the real DOM
   :last-child, so a plain ".a4-page:last-child" selector silently missed
   the actual last page in exactly that case. Mirrors
   .print-page:last-child .print-page-content exactly in intent (print
   has no such extra sibling, so :last-child stays correct there), and
   for the same reason: buildPrintDocumentPages's greedy fit loop always
   packs every non-last page to capacity, so only the last page can ever
   be under-full. Items stay naturally top-aligned here (default
   flex-start - no justify-content override needed).

   OWNER CONTRACT (confirmed against a real invoice reference): the
   bottom-pinned trailer group on the last page reads totals, then
   payment text, then the footer - in that DOM/visual order, all pinned
   to the bottom together. An earlier iteration pinned totals to the
   table instead (margin-top: 0), which left a large, visually wrong
   empty gap between totals and the footer whenever the last page's
   items didn't already fill it (confirmed via a real screenshot). A
   later iteration tried moving payment text entirely before totals to
   keep it out of the trailer group, but the real reference invoice
   confirmed payment text belongs directly between totals and the footer
   - so it stays in its original source position (invoice_page.html:
   totals, then payment, then footer) and gets its own compact spacing
   below (see ".a4-page-last .a4-page-content > .invoice-payment-text").
   margin-top: auto here claims all of the page's leftover vertical
   space, pulling totals - and, right after it, payment text and the
   footer in its own separate .a4-page-footer - down together as one
   bottom-pinned group; margin-bottom: 1.4rem (this element's own real,
   measured line-height - 22.4px at the 16px/1.4 base type scale used
   throughout this page) reads as "about one line" between totals and
   payment text right after it. */
.a4-page-last .a4-page-content > .invoice-totals {
  margin-top: auto;
  margin-bottom: 1.4rem;
}

/* Payment text sits directly between totals and the footer (OWNER
   CONTRACT, confirmed against a real invoice reference) - no auto margin
   of its own (totals above already claims the page's leftover space and
   pulls the whole trailer down together); margin-bottom: 1.4rem (same
   "about one line" measurement as totals above) keeps payment from
   sitting flush against the footer right below it, without moving the
   footer itself: the footer lives in its own separate .a4-page-footer
   row (flex: 0 0 auto) right after .a4-page-content, whose own total
   height is unchanged by this margin. */
.a4-page-last .a4-page-content > .invoice-payment-text {
  margin-top: 0;
  margin-bottom: 1.4rem;
}

.a4-page-footer {
  flex: 0 0 auto;
}

/* Same reasoning as .print-page-footer .invoice-footer in print.css: the
   footer's on-screen .editable margin-top (-0.35rem, see .editable below)
   is part of that class's hitbox trick for a plain single-box editable
   element inline in the page flow - not appropriate once the footer sits
   alone in its own dedicated, already-separated footer area.
   padding-top/border-top are the same geometry-parity fix as
   ".a4-page-content > .invoice-payment-text" below, applied to the
   canonical (non-clone) footer instance: confirmed via the real print.css
   ".editable" cascade (border: none !important; padding: 0; margin: 0 -
   same 0,1,0 specificity as .invoice-footer's own border-top/padding-top,
   print.css loaded after app.css so it wins the tie) that real print
   renders this footer with zero padding-top and zero border-top, not the
   0.75rem padding-top + 1px border-top .invoice-footer sets on its own
   below. This rule now applies to a footer on every physical page (new
   contract - see app.js newPage()/renderScreenPageBoxes), including
   pages the pagination algorithm packs with items right up to capacity -
   exactly where this leftover ~13px can overflow .a4-page past 297mm.
   Vertical-only, same as the payment-text fix: margin-left/right
   (horizontal centering) is untouched since it never affects page
   height. */
.a4-page-footer .invoice-footer {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

/* Empty payment text / empty footer: print has nothing there at all (the
   placeholder is screen-only and cleanClone strips it), so on screen the
   placeholder must not take any layout space either - otherwise a page
   that print packs to capacity would be taller on screen than 297mm.
   The payment placeholder is lifted into the totals' own 1.4rem bottom
   gap (it is one 1.4rem line, so it fits exactly) and the payment block
   collapses to zero like print's :empty rule; the footer placeholder hangs
   below the zero-height footer into the page's 20mm bottom margin, still
   inside the physical page. Both stay clickable via their parent link. */
.a4-page-last .a4-page-content > .invoice-payment-text:has(> .placeholder) {
  position: relative;
  margin-bottom: 0;
}

.a4-page-last .a4-page-content > .invoice-payment-text > .placeholder {
  /* left stays auto: the static position, i.e. after the link's own
     hitbox padding-left, so the hint lines up with the content edge. */
  position: absolute;
  bottom: 0;
  white-space: nowrap;
}

.a4-page-footer .invoice-footer:has(> .placeholder) {
  position: relative;
}

.a4-page-footer .invoice-footer > .placeholder {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
}

/* Screen-preview-only, non-interactive repeats (see app.js
   markVisualOnlyClone): a continuation fragment of a split oversized row,
   or a repeated totals/payment/footer. Layout classes (.editable,
   .invoice-footer, ...) are deliberately left on these so they render
   with identical geometry to the real thing - only interaction is
   suppressed, so hovering one never shows an edit affordance (border
   highlight, outline) for content that isn't actually clickable. */
.a4-clone-static {
  pointer-events: none;
}

/* Measured root cause of oversized-row .a4-page pages running taller
   than physical 297mm: under real @media print, .editable's own padding/
   margin/border (see .editable below) are unconditionally zeroed for
   every .editable element, footer and payment text included (print.css).
   On screen that reset never applied, so a repeated (non-canonical)
   footer/payment clone carried .editable's full padding-bottom (0.35rem)
   and its otherwise-invisible dashed-transparent border-bottom (1px) as
   real, measured extra height - confirmed via matched real
   @media=print vs screen measurements of the same fragment page, not
   assumed. Deliberately scoped to ".a4-clone-static" combined with the
   specific element class (not a bare ".a4-clone-static" rule): a
   continuation-fragment .invoice-item-row-clone is also marked
   .a4-clone-static, and its own needed padding/margin come from the
   shared Stage 1 grid rule above - a same-specificity, order-dependent
   collision that a bare ".a4-clone-static { padding: 0 }" rule would risk
   winning by accident and silently breaking Stage 1's column geometry for
   exactly that element. Never applied to the one canonical (non-clone)
   footer/payment instance, so its normal on-screen appearance (padding,
   separating border-top line) is completely unaffected - only the
   repeated, non-canonical instances now render with the exact same zero
   padding/margin/border real print already gives them.

   .invoice-footer's own margin-left/right: auto (see .invoice-footer
   below) is deliberately NOT included in this zeroing: a plain blanket
   "margin: 0" here was a confirmed regression - it zeroed those too,
   so every repeated footer clone (page 2+, since the one canonical,
   non-clone footer always lands on page 1 - see app.js) lost its
   horizontal centering and rendered flush-left while page 1's footer
   stayed centered. Split into its own rule below so the footer clone
   keeps the exact same horizontal geometry as the canonical instance -
   only vertical margin is zeroed here, matching real print exactly as
   before. */
.invoice-payment-text.a4-clone-static {
  padding: 0;
  margin: 0;
  border: none;
}

/* Same real-print vertical-zero parity as the clone rule above, but
   scoped separately from .invoice-payment-text: unlike payment text,
   .invoice-footer centers itself horizontally via margin-left/right:
   auto (see .invoice-footer below), not via any padding-based hitbox
   trick, so a blanket "margin: 0" here would silently strip that
   centering from every repeated clone - the exact bug this rule now
   fixes. margin-left/right: auto is set explicitly (not just left
   unset) so it wins outright over print.css's real @media print
   .editable rule (margin: 0) regardless of cascade order: this
   selector's two classes are exactly as specific as .editable (both
   0,1,0), so leaving margin-left/right unset here would let source
   order alone decide, and print.css (loaded after app.css) would win
   with margin-left/right: 0 - reintroducing the same bug under real
   print. Only margin-top/bottom are zeroed (real print's own vertical
   budget); padding/border stay fully zeroed as before. */
.invoice-footer.a4-clone-static {
  padding: 0;
  margin-top: 0;
  margin-bottom: 0;
  margin-left: auto;
  margin-right: auto;
  border: none;
}

/* Same root cause, found on the one canonical (non-clone) payment text:
   whichever page it first lands on - often a busy last page also
   carrying items, totals and the footer, for an invoice with no
   oversized rows - measurably ran taller than 297mm from the same
   .editable padding-top/bottom (0.35rem each) and dashed-transparent
   border (1px each) real print never has. Vertical-only and scoped with
   ">" to this one direct-child position deliberately: unlike the
   clone-only rule above, .invoice-payment-text's own documented
   edge-to-edge hitbox trick (padding-left/right + the matching negative
   margin, see .invoice-payment-text below) must stay completely intact
   for the one genuinely interactive instance - only the vertical
   properties that padding/border a real print never budgets for are
   removed, never padding-left/right or margin-left/right.
   margin-bottom is included here too, correcting an earlier assumption:
   .invoice-payment-text's own explicit margin-bottom: 1.5rem (app.css,
   below) looks like deliberate spacing but is not actually what real
   print renders - .invoice-payment-text carries .editable too, and
   print.css's own .editable rule (margin: 0, same 0,1,0 specificity,
   print.css loaded after app.css) wins that tie under real @media print,
   zeroing margin-bottom right along with margin-top/left/right. Confirmed
   via matched real @media=print vs screen measurement of the same
   canonical last page: leaving margin-bottom: 1.5rem in this screen-only
   rule left the screen .a4-page ~24px taller than the physical 297mm
   real print pagination already budgets for (#print-document's own
   matching zero-margin rule below already reflects this). */
.a4-page-content > .invoice-payment-text {
  padding-top: 0;
  padding-bottom: 0;
  margin-bottom: 0;
  border-top: none;
  border-bottom: none;
}

/* Measured root cause #2 of the same oversized-row .a4-page overflow -
   and a confirmed follow-up regression from the first attempted fix:
   the "+ Position hinzufügen" control (app.js renderScreenPageBoxes) is
   screen-only - print's own cleanClone strips every .screen-only
   element, so buildPrintDocumentPages never allocates any space for it
   on any page. A height: 0 + overflow: visible wrapper *inside* the
   page's own item list (tried first) reported zero height to
   .a4-page-content's flex sizing, matching print's budget - but the
   button's real, visible content still had to render starting exactly
   there, with no reserved space of its own; whenever the last item's
   content already filled the page close to capacity, the button
   visually ran straight into the footer below it (confirmed in Chrome:
   negative gap between the button's own bottom edge and the footer's
   top edge). app.js now appends this host as .a4-pages's own plain flex
   child instead, directly after the last physical page (app.js), never
   inside any .a4-page's own box, so it can never overlap that page's
   footer/totals/payment and never affects that page's own height in the
   first place.

   Geometry, per the owner's requirement that it read as belonging to the
   last sheet (both values measured as wrong in Chrome before this rule -
   the button sat 75.6px left of the last sheet's content edge):

   - padding-left: 20mm is .a4-page's own padding (see there), so the
     button's left edge lands exactly on the last sheet's left CONTENT
     edge rather than on its outer paper edge. width: 210mm stays the
     paper width (global border-box keeps the outer box at 210mm), so the
     host still aligns with the pages under .a4-pages's align-items:
     center.
   - margin-top cancels .a4-pages's own 2rem inter-page gap exactly, so
     the only vertical space left between the sheet's bottom edge and the
     button is .add-item-button's own existing margin-top (0.5rem) - the
     same small gap that control already has under the item table on a
     single-page invoice. No new spacing constant is introduced. */
.a4-add-item-host {
  width: 210mm;
  padding-left: 20mm;
  margin-top: -2rem;
}

/* Normal placement (owner requirement): the control directly below the last
   item row, inside the last item section (app.js placeAddItemButton). Out of
   flow and zero height, so it adds nothing to the section, the page or any
   pagination measurement; the visible gap under the last row is the button's
   own margin-top (0.5rem). left: 0 is the section's left edge - the same
   content edge the column headings and item text start at. The .a4-page
   scope keeps position: relative off every print-document section. */
.a4-page .invoice-items {
  position: relative;
}

.a4-add-item-anchor {
  position: absolute;
  top: 100%;
  left: 0;
  height: 0;
}

/* --- Print document (built by app.js just before printing) --- */

/* Off screen during normal editing (out of flow, so it never affects the
   visible editor's layout) but NOT display: none — it must stay
   measurable so app.js can lay out real content into real, correctly
   sized .print-page boxes before print. print.css brings it into normal
   flow and hides .invoice-page instead once actually printing. */
#print-document {
  position: fixed;
  top: 0;
  left: -99999px;
  visibility: hidden;
}

/* Confirmed Stage 2 regression: buildPrintDocument()'s own pagination
   decision - which page each row/the trailer lands on - is made by
   measuring real, rendered clones inside #print-document at whatever
   moment it runs. renderScreenPageBoxes calls it once on page load,
   normally while @media print is NOT active (the user hasn't printed
   yet) - so without this rule, the footer/payment clones it measures
   against still carried .editable's full screen-time padding/margin/
   border (print.css's own reset only applies once @media print is
   actually active), making the auto-sized footer row of .print-page's
   own grid measurably taller than a real print run would ever produce,
   leaving less room for the 1fr content row above it and changing which
   page an item lands on - confirmed via matched real @media=print vs
   screen-time pagination results for the same 28-item invoice (screen:
   [5,6,6,6,5,0], real print: [5,6,6,6,5] - trailer needed a 6th page on
   screen only). Scoped to these two classes specifically, not a bare
   "#print-document .editable" rule: that would also catch
   .invoice-item-row (also .editable) at a higher specificity than
   Stage 1's own shared grid rule, silently overriding its carefully
   tuned padding/margin for every row buildPrintDocumentPages measures.

   Split into two rules below (was one shared rule): a blanket
   "margin: 0" on #print-document .invoice-footer zeroed its
   margin-left/right: auto too (see .invoice-footer below), silently
   flush-lefting every page's footer in the actual printed/PDF output -
   #print-document is exactly what real @media print renders, not just
   what pagination measures. Vertical-only now, matching
   .invoice-footer.a4-clone-static's screen-side fix for the same
   reason and same horizontal geometry. */
#print-document .invoice-payment-text {
  padding: 0;
  margin: 0;
  border: none;
}

/* Same pagination-fidelity reason as the rule above, for the sender block:
   it carries "editable" now (it is a click target on the invoice itself, see
   .invoice-company-info below), so inside #print-document it would otherwise
   be measured with .editable's screen-time padding/margin/border while
   @media print is not yet active — making the header clone taller than a
   real print run for an address long enough to make the sender, rather than
   the header's own min-height, decide the header's height. Zeroed
   here so the measured header matches what actually prints. Scoped to this
   one class, not a bare "#print-document .editable" (see above for why that
   would break .invoice-item-row); no auto margins involved here, unlike
   .invoice-footer below, so both axes are simply zeroed. */
#print-document .invoice-company-info {
  padding: 0;
  margin: 0;
  border: none;
}

/* #print-document's own ID selector gives this a higher specificity
   (1,1,0) than print.css's real @media print .editable rule
   (margin: 0, specificity 0,1,0), so explicitly setting margin-left/
   right: auto here
   (not just omitting them) wins outright and keeps every page's footer
   - including the real, actually-printed/PDF output, not only the
   pagination measurement - centered exactly like .invoice-footer's own
   base rule below. Only margin-top/bottom are zeroed, same as before. */
#print-document .invoice-footer {
  padding: 0;
  margin-top: 0;
  margin-bottom: 0;
  margin-left: auto;
  margin-right: auto;
  border: none;
}

/* One .print-page per physical A4 page. Sized to the exact physical
   page (border-box, so 210x297mm already includes the padding below) —
   not derived from any content or item count. The grid gives the
   footer row exactly the height its own content needs and the content
   row everything else, so the footer is always flush at that page's
   bottom purely by construction; app.js only ever needs to ask the DOM
   "does this content still fit" (scrollHeight vs. clientHeight) to
   decide how many rows belong on a page — no page-height arithmetic of
   any kind. Declared outside any @media block so it is already correct
   while #print-document is still off-screen (see above), before print
   media formally activates. */
.print-page {
  width: 210mm;
  height: 297mm;
  box-sizing: border-box;
  /* 20mm margin on top/left/right and 2/3 of that at the bottom (owner
     requirement: the gap under the footer is about a third smaller), on
     every physical page - this is the only place print margins are set
     (see print.css's @page margin: 0 comment), so it applies identically
     to page 1 and every following page without any per-page
     special-casing. */
  padding: 20mm;
  padding-bottom: calc(20mm * 2 / 3);
  background: #fff;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  break-after: page;
  page-break-after: always;
}

.print-page:last-child {
  break-after: auto;
  page-break-after: auto;
}

.print-page-content {
  min-height: 0;
  overflow: hidden;
}

/* Every non-last .print-page is always packed to capacity by
   buildPrintDocumentPages's greedy fit loop (a page only ever ends because
   the next row didn't fit), so only the last physical page can ever be
   under-full - e.g. a totals/payment trailer alone on an otherwise empty
   fresh page. .print-page-content's own box (width/height/overflow) is
   exactly what app.js measures via scrollHeight/clientHeight to decide
   page breaks, so it must keep an identical fixed size on every page,
   :last-child included - only how its content is *aligned inside* that
   unchanged box differs here. Items stay naturally top-aligned (default
   flex-start - this rule used to set justify-content: center here
   instead, floating the whole trailer in the middle of the leftover
   space); totals (see its own margin-top: auto rule below) sinks to the
   bottom of this box, with payment text right after it, forming one
   bottom-pinned trailer group that sits directly above the footer.
   Verified live against buildPrintDocument() (including mid-pagination,
   while a page is transiently :last-child) that page count and per-page
   row/fragment distribution are byte-identical to before this change - it
   changes no measurement, only how already-decided content is aligned. */
.print-page:last-child .print-page-content {
  display: flex;
  flex-direction: column;
}

/* OWNER CONTRACT (confirmed against a real invoice reference): the
   bottom-pinned trailer group on the last page reads totals, then
   payment text, then the footer - in that DOM/visual order, all pinned
   to the bottom together. An earlier iteration pinned totals to the
   table instead (margin-top: 0), which left a large, visually wrong
   empty gap between totals and the footer whenever the last page's
   items didn't already fill it (confirmed via a real screenshot). A
   later iteration tried moving payment text entirely before totals to
   keep it out of the trailer group, but the real reference invoice
   confirmed payment text belongs directly between totals and the footer
   - so it stays in its original source position (invoice_page.html:
   totals, then payment, then footer) and gets its own compact spacing
   below (see "#print-document .print-page:last-child .print-page-
   content > .invoice-payment-text"). margin-top: auto here claims all of
   the last page's leftover vertical space, pulling totals - and, right
   after it, payment text/the footer - down together as one bottom-pinned
   group. margin-bottom: 1.4rem (same "about one line" gap as the
   screen-side ".a4-page-last" rule - see its own comment in app.css for
   the exact line-height match) keeps totals from sitting flush against
   payment text right below it. Mirrors ".a4-page-last .a4-page-content >
   .invoice-totals" on the screen side exactly (print has no
   ".a4-add-item-host"-style extra sibling after its last page, so plain
   :last-child stays correct here - only the screen-side selector needed
   the ".a4-page-last" class fix). */
.print-page:last-child .print-page-content > .invoice-totals {
  margin-top: auto;
  margin-bottom: 1.4rem;
}

/* Payment text sits directly between totals and the footer (OWNER
   CONTRACT, confirmed against a real invoice reference) - no auto margin
   of its own anymore (totals above already claims the page's leftover
   space and pulls the whole trailer down together). #print-document-
   scoped so this ID selector (specificity 1,4,0) outranks the existing
   "#print-document .invoice-payment-text { margin: 0 }" pagination-
   measurement-accuracy rule above (also ID-scoped, same specificity
   class) - a plain class selector here would lose that tie and be
   silently zeroed back out (that includes margin-bottom below too, not
   just margin-top).

   margin-bottom: 1.4rem (same "about one line" gap as the screen-side
   ".a4-page-last" rule - see its own comment in app.css for the exact
   line-height match) keeps payment text from sitting flush against the
   footer right below it. Read into the SAME measurement this selector
   already feeds (buildPrintDocumentPages' contentOverflows check runs
   live against whatever this element's current CSS resolves to), so real
   print and the pagination decision that produced it never disagree
   about this element's height - never a separate, hand-tuned pixel
   constant. The footer's own position is unaffected: it lives in its own
   separate .print-page-footer grid row, sized independently by the
   page's own grid-template-rows. */
#print-document .print-page:last-child .print-page-content > .invoice-payment-text {
  margin-top: 0;
  margin-bottom: 1.4rem;
}

/* When there is no payment text at all, cleanClone (app.js) strips the
   screen-only "+ Zahlungstext" placeholder span, leaving this element
   genuinely empty in the print DOM - :empty here zeroes its margins so it
   contributes no extra space of its own, keeping the gap between totals
   and the footer to totals' own margin-bottom above (~one line), not
   doubled up by an invisible second spacer. Print-only: the live screen
   node always shows the (visible, screen-only) placeholder, so it is
   never actually :empty there. */
#print-document .print-page:last-child .print-page-content > .invoice-payment-text:empty {
  margin-top: 0;
  margin-bottom: 0;
}

/* Owner-confirmed requirement (new contract): the footer must be visible
   on every physical page, not just the last one. app.js's newPage() now
   attaches an independent footer clone to every .print-page it creates,
   from the moment the page exists - so this row always has real content
   to size itself against, on every page, and grid-template-rows:
   minmax(0, 1fr) auto above always gives it exactly the height its own
   content needs while the content row above claims the rest. */
.print-page-footer {
  align-self: end;
}

/* The edit affordance is drawn with an outline, never a border: an
   outline takes no layout space, so the on-screen sheet consumes exactly
   the same height as the printed page (where print.css drops every edit
   decoration). The former 1px transparent border added 2px to every
   editable block - every item row included - on screen only, so the screen
   preview and the real print disagreed about where pages break.
   outline-offset: -1px draws it exactly where that border used to sit. */
.editable {
  display: block;
  color: inherit;
  text-decoration: none;
  border-radius: 6px;
  padding: 0.35rem 0.5rem;
  margin: -0.35rem -0.5rem;
}

.editable:hover {
  outline: 1px dashed var(--primary);
  outline-offset: -1px;
  background: rgba(26, 77, 143, 0.05);
}

.placeholder {
  color: var(--primary);
  font-style: italic;
}

.invoice-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
  /* The meta block (Rechnung Nr./Datum/Seite) sits in .invoice-meta-row
     below, 2.8rem lower than the recipient. 152px = the logo safe zone's
     own height (140px, see .logo-slot) + the former 0.75rem logo-to-meta
     gap: with a short sender this floor keeps the meta block's top at
     least 152px below the header's top edge, so it always clears the
     logo; a taller sender pushes both blocks down together. */
  min-height: calc(152px - 1.5rem - 2.8rem);
  /* Owner-confirmed requirement: the logo/card must be a fully
     out-of-flow overlay (see .logo-slot below) - position: relative
     here gives it a stable containing block anchored to this header's
     own top-right corner (already inside .invoice-page/.print-page's
     20mm padding), identically in screen (.a4-page-content) and print
     (.print-page-content) - .invoice-header is cloned/moved as one
     shared element into both, never duplicated per context. */
  position: relative;
}

/* Holds only the logo/card, which overlays independently (see .logo-slot
   below) - the invoice number/date block lives in .invoice-meta-row. */
.invoice-header-right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  min-width: 0;
}

/* Owner-confirmed requirement: an uploaded logo/card of any backend-
   accepted source dimensions/aspect ratio (see app/web/images.py's own,
   separate MAX_LOGO_DIMENSION/MAX_LOGO_PIXELS policy - not changed or
   widened here) must NEVER influence invoice layout - no pushing the
   table, recipient, invoice meta, header height, or page count, no
   matter how large, small, wide, or tall the accepted source image is.
   Layout is independent of accepted image dimensions. Root
   cause of the prior bug (measured in a real Chrome session, see the
   diagnosis this fixes): the logo sat as an ordinary flex child of
   .invoice-header-right, so its own rendered height (up to 140px)
   directly grew that flex column - and, through .invoice-header's own
   align-items: flex-start, the whole header - pushing every element
   below it down by exactly that amount. Fixed by taking the card fully
   out of normal document flow: position: absolute anchored to
   .invoice-header's own top-right corner (see position: relative there)
   means no sibling or ancestor box ever measures this element's size -
   .invoice-header-right's own layout proceeds exactly as if the card
   did not exist.

   220x140 is not a guessed size: it is this project's own existing,
   already-shipped logo safe zone (previously .invoice-logo's own
   max-width/max-height, measured directly from the real, live header
   before this change) - now the FIXED size of the safe zone itself,
   independent of whatever image is actually loaded inside it. margin: 0
   overrides .editable's inline-hitbox negative-margin trick (meant for
   a plain text link, not a dedicated absolutely-positioned box) so
   top: 0 / right: 0 land exactly on the safe zone's real corner, never
   offset by it. overflow: hidden is a hard backstop, never relied on in
   practice (.invoice-logo's own max-width/max-height below already keep
   any image inside this box) - only guards against a future change to
   that rule accidentally letting oversized content escape the zone.
   text-align: right anchors content (the image, an inline-replaced
   element, or the "+ Logo" placeholder text) to the zone's own
   top-right corner - matching where the logo already sat before this
   change - using plain block/inline flow rather than flex or grid,
   simpler for a box with exactly one visible child at a time.

   Known, measured, purely cosmetic screen/print micro-variance (kept
   flex and plain-block both tested, same result either way, so this is
   not a flex-specific issue): for a source image whose aspect ratio
   makes height the constraining dimension, real @media print resolves
   .invoice-logo's percentage max-height against this box to an exact
   140px, while normal screen layout resolves the same DOM/CSS to
   ~138.67px (~1% smaller) - a sub-pixel difference in Chromium's
   replaced-element sizing between its screen and print layout passes,
   not in this box's own geometry (.logo-slot itself measures byte-
   identical 220x140 in both contexts, and table/recipient/meta
   positions are byte-identical too). Invisible in practice and outside
   this task's contract (layout invariance, not the card's own render
   size to the sub-pixel) - noted here so it is never mistaken for a
   regression if re-measured later. */
.logo-slot {
  position: absolute;
  top: 0;
  right: 0;
  width: 220px;
  height: 140px;
  margin: 0;
  padding: 0;
  text-align: right;
  overflow: hidden;
  /* Owner-confirmed requirement: the card must render in its exact
     source shape - rectangular stays rectangular, square stays square -
     with no artificial rounding of any kind. .logo-slot also carries
     the shared "editable" class (for its click hitbox/hover affordance,
     same as every other editable region on this page), whose base rule
     sets border-radius: 6px - harmless for a plain hover outline, but
     combined with this box's own overflow: hidden it would visibly clip
     a few pixels off every image's corners. Zeroed here, scoped to only
     this box, so the hover/click behavior every other .editable element
     shares is completely unaffected. */
  border-radius: 0;
}

/* max-width/max-height: 100% (not a fixed px value) caps the image to
   whatever .logo-slot's own fixed 220x140 box actually provides - the
   two are numerically identical today, but expressed this way the image
   is always bounded by its real container, never a second, separately
   maintained constant that could drift out of sync with it.
   object-fit: contain preserves aspect ratio and never upscales past
   the source's own resolution or stretches it: a source image smaller
   than the safe zone renders at its own natural size (never stretched
   to fill the zone); a source image larger than the safe zone scales
   down to fit inside it in both dimensions, without cropping - this CSS
   mechanism itself has no resolution ceiling of its own (confirmed
   directly against a genuine 5000x5000 image, well past the backend's
   own separate MAX_LOGO_DIMENSION = 4096 upload cap - see
   app/web/images.py, not changed or widened here). */
.invoice-logo {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* .invoice-header is a flex row (company info left, logo/meta right); a
   flex item's default min-width: auto stops it shrinking below its
   unbroken content's width, so without min-width: 0 a long company
   name/address line could still push past max-width and into the right
   half. overflow-wrap catches a single long word with no natural break
   points the same way.

   This element is also the Absender click target (an <a href="#dlg-sender">
   carrying the shared "editable" class, like the recipient block), so it
   inherits that class's hover affordance and its padding/negative-margin
   hitbox trick — whose two halves cancel out exactly, leaving the company
   text rendered in the same place it was before it became clickable. No
   geometry of its own is added or overridden here for that. */
.invoice-company-info {
  text-align: left;
  font-size: 0.9rem;
  max-width: 50%;
  min-width: 0;
  overflow-wrap: anywhere;
}

.invoice-company-name {
  font-weight: 700;
  font-size: 1.05rem;
}

/* Owner requirement: the recipient (left) starts higher than the invoice
   number/date block (right) - recipient directly under the sender, the
   meta block 2.8rem (two body lines) lower and below the logo. Sides stay
   as they are. The recipient's max-width (60%) keeps it clear of the logo
   safe zone (220px at the right edge) it sits beside. */
.invoice-meta-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}

/* max-width: 60% alone doesn't stop a single unbroken run of characters
   (no spaces) from overflowing this box visually - overflow-wrap: normal
   (the default) forbids breaking within it. overflow-wrap: anywhere lets
   it wrap inside the already-correct 60% width instead; word-break as a
   fallback for the same case. .invoice-recipient is a plain block (not a
   flex/grid item), so no min-width: 0 is needed here. */
.invoice-recipient {
  flex: 0 1 auto;
  max-width: 60%;
  min-height: 3rem;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.invoice-recipient-name {
  font-weight: 600;
}

/* Without a width cap, a very long Rechnung Nr. has no natural break
   point and pulls this block wide enough to run across most of the page.
   280px matches .invoice-totals's own fixed-width convention below -
   comfortably wider than any normal invoice number, and still well inside
   the right half of the page for a runaway long one. min-width: 0 lets the
   block actually shrink to that cap instead of stopping at its unbroken
   content's width; overflow-wrap wraps the number itself.

   Selector is ".invoice-meta-row > .invoice-meta" (specificity 0,2,0),
   not a bare ".invoice-meta" (0,1,0), on purpose: this block also carries
   "editable", and print.css's real @media print ".editable { margin: 0 }"
   (also 0,1,0, loaded later) would win the tie and wipe out the
   margin-top below - in the real PDF the Rechnung Nr./Datum lines would
   then print level with the recipient, next to the logo card. The higher
   specificity keeps this one offset in print too, while every other
   .editable print reset (border, padding, hover, the hitbox side/bottom
   margins) still applies to this block unchanged. */
.invoice-meta-row > .invoice-meta {
  text-align: right;
  max-width: 280px;
  min-width: 0;
  overflow-wrap: anywhere;
  flex: 0 0 auto;
  /* Owner requirement: two body lines (2 x 1.4rem) below the recipient's
     top edge. Independent of whether a logo/card is set or what size/shape
     it is (the card is a fully out-of-flow overlay); clearing the logo is
     guaranteed by .invoice-header's min-height above. */
  margin-top: 2.8rem;
  margin-right: 0;
  margin-bottom: 0;
  margin-left: 0;
  /* No .editable hitbox padding/negative margin on this block at all, on
     any side: print drops them, so on screen they changed this block's
     geometry - the top padding made the screen header 5.6px taller, the
     side padding made the text box 16px narrower than in print, so a long
     invoice number wrapped onto more lines on screen than on paper and the
     screen page split drifted from the print one. The text box is now
     identical on screen and in print; the hitbox lives in ::before below. */
  padding: 0;
  /* Containing block for the ::before edit hitbox below. */
  position: relative;
}

/* The meta block's edit hitbox and hover decoration, out of flow: the same
   area .editable's padding/negative margin gave it, without touching the
   text box (see above). Clicks on the pseudo-element hit the link itself. */
.invoice-meta-row > .invoice-meta::before {
  content: "";
  position: absolute;
  inset: -0.35rem -0.5rem;
  border-radius: 6px;
}

.invoice-meta-row > .invoice-meta:hover {
  outline: none;
  background: none;
}

.invoice-meta-row > .invoice-meta:hover::before {
  outline: 1px dashed var(--primary);
  outline-offset: -1px;
  background: rgba(26, 77, 143, 0.05);
}

/* "Seite X von Y", the third meta line, directly under Datum - on page 1 and
   in every later page's compact repeat (.page-continuation-meta below). In
   flow, so it is part of the meta block's own measured height and can never
   reach into the item table below. Y comes from app.js, read off the
   finished page list. */
.invoice-page-number {
  white-space: nowrap;
  font-size: 0.85rem;
  color: #333;
}

/* Owner-confirmed requirement: every physical page after the first repeats a
   compact meta block - Rechnung Nr., Datum, Seite X von Y - directly above
   that page's column headings (app.js continuationMetaClone, appended by
   newPage()/renderScreenPageBoxes). Same cloned content as page 1's block,
   deliberately NOT the same geometry: .invoice-meta's offset below the
   recipient is page 1's concern, so the clone drops that class and gets its
   own box here.

   margin-left: auto right-aligns the block itself (it keeps .invoice-meta's
   max-width via the width cap below, and its own text-align: right), matching
   where the number/date sit on page 1. margin-bottom is .invoice-header's own
   existing 1.5rem, so the gap down to the column headings reads the same as
   on page 1 - no new spacing constant.

   The page line is in flow, as on page 1, which is exactly what makes the
   repeat part of the height the existing fit loop already measures for these
   pages. */
.page-continuation-meta {
  display: block;
  color: inherit;
  text-decoration: none;
  text-align: right;
  max-width: 280px;
  min-width: 0;
  margin-left: auto;
  margin-bottom: 1.5rem;
  overflow-wrap: anywhere;
}

.invoice-meta-label {
  font-weight: 600;
}

.invoice-items {
  margin-bottom: 1rem;
}

.invoice-items-head,
.invoice-item-row,
.invoice-item-row-clone {
  display: grid;
  /* Fixed columns sized to their measured content in Chrome (16px base):
     a 5-digit EUR value "99999,99 €" 79px, "Menge" heading 43px, MwSt.
     amount over its own "(19 %)" line 79px. Everything left over goes to
     Beschreibung, the main column (owner requirement: no separate
     Leistungsdatum column - the date is a line of its own inside
     Beschreibung, see .item-service-date). */
  grid-template-columns: 1fr 5rem 3rem 5rem 5rem;
  gap: 0.5rem;
  padding: 0.4rem 0.5rem;
  /* .invoice-item-row is also .editable, whose negative margin (see
     .editable below) is a deliberate hitbox trick for plain single-box
     editable elements. On a grid row sharing this exact
     grid-template-columns with .invoice-items-head (a plain, non-.editable
     div), that same negative margin instead widens the row's grid
     container ~16px past the header's, so the flexible 1fr Beschreibung
     column resolves to two different pixel widths. margin: 0 here
     neutralizes it for both selectors (a no-op for .invoice-items-head,
     which never had it) so header and row grid containers are always the
     same width. .invoice-item-row-clone (Stage 2, see app.js
     markVisualOnlyClone) is the screen-preview-only, non-interactive
     stand-in for a continuation fragment of a split oversized row -
     same geometry, deliberately excluded from the ":hover" edit affordance
     below (see .invoice-item-row:hover) since it isn't actually
     clickable. */
  margin: 0;
}

.invoice-items-head {
  font-size: 0.85rem;
  font-weight: 600;
  border-bottom: 1px solid #1a1a1a;
}

.invoice-items-head > span:first-child {
  text-align: left;
}

.invoice-item-row,
.invoice-item-row-clone {
  border-bottom: 1px solid var(--border);
  /* The separator line is screen-only (print.css drops every border on
     .editable, rows included), so its 1px comes out of the bottom padding:
     a row consumes exactly the same height on screen as in print, where
     print.css restores the full 0.4rem padding. Without this, every screen
     row was 1px taller than its printed counterpart. */
  padding-bottom: calc(0.4rem - 1px);
  border-left-width: 0;
  border-right-width: 0;
  /* Grid items default to align-items: stretch, so Preis/
     Menge/Betrag/MwSt. fill the row's full height (set by the tallest
     cell — usually multiline Beschreibung) and sit vertically centered
     within it once their own content height is subtracted. start instead
     anchors each cell's own (single-line) content to the row's top edge —
     level with Beschreibung's first line, not its middle. Beschreibung
     itself is unaffected: it's normally the cell whose natural height
     *sets* the row height, so top-aligning it within a space equal to its
     own height doesn't move it at all. */
  align-items: start;
}

.invoice-item-row:hover {
  outline: 1px dashed var(--primary);
  outline-offset: -1px;
}

/* Oversized-row edge case (app.js renderScreenPageBoxes): the one
   .invoice-item-row-clone that stands in for the canonical, hidden live
   row - splitOversizedRow's own first fragment, given that row's real
   href - is the one genuine click target for the item, so it gets the
   same edit affordance as a real .invoice-item-row; the [href] scoping
   keeps every other (non-interactive, no href, pointer-events: none via
   .a4-clone-static) continuation-fragment clone unaffected. */
.invoice-item-row-clone[href]:hover {
  outline: 1px dashed var(--primary);
  outline-offset: -1px;
}

.numeric {
  text-align: right;
}

/* Beschreibung is the flexible 1fr column; without min-width: 0 it would
   still grow to its unbroken content's intrinsic width (grid items default
   to min-width: auto) and push every fixed-width column after it out of
   place. Always left-aligned, flush to the column's left edge, regardless
   of content length. */
.item-description-cell {
  min-width: 0;
  text-align: left;
}

/* pre-wrap keeps typed newlines as real line breaks; overflow-wrap lets a
   single very long word (no spaces at all) break too, instead of
   overflowing the column. A block of its own, so the Leistungsdatum line
   always starts on a new line below the description text. */
.item-description {
  display: block;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* "Leistungsdatum: dd.mm.yyyy" on its own line inside Beschreibung (owner
   requirement). A continuation fragment of a split row has it blanked
   (app.js blankNonDescriptionFields), and an empty block takes no height. */
.item-service-date {
  display: block;
}

/* MwSt. is two fixed lines: the VAT amount, then its "(xx %)" rate on a
   line of its own (that is what lets the column stay narrow and give
   Beschreibung the width). Neither line may ever break internally — no
   "%)" or "€" stranded on a line of its own. */
.item-vat {
  white-space: nowrap;
}

.item-vat-rate {
  display: block;
}

.empty-row {
  color: #777;
  font-style: italic;
  padding: 0.5rem;
}

.add-item-button {
  display: inline-block;
  margin-top: 0.5rem;
  color: var(--primary);
  text-decoration: none;
  border: 1px dashed var(--primary);
  border-radius: 6px;
  padding: 0.4rem 0.8rem;
}

/* Same flex-column + auto-margin mechanism as .invoice-footer below: pins
   totals directly above the footer on a short invoice instead of rising
   directly under the item table (only the on-screen editor — print
   positions the footer via its own page-content/page-footer grid rows in
   app.js and isn't affected by this). For a tall invoice the table alone
   already fills the page, so this auto margin resolves to 0 and totals
   simply flows right after the table, same as before - payment text and
   the footer, right after totals in the source (invoice_page.html),
   travel down together with it as one bottom-pinned trailer group (OWNER
   CONTRACT, confirmed against a real invoice reference: totals, then
   payment text, then the footer). margin-bottom: 1.4rem is this
   element's own real, measured line-height (22.4px at the 16px/1.4 base
   type scale used throughout this page) - "about one line" between
   totals and payment text right below it, never a guessed pixel gap. */
.invoice-totals {
  margin-top: auto;
  margin-bottom: 1.4rem;
  margin-left: auto;
  max-width: 280px;
  text-align: right;
}

.invoice-totals div {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
}

/* Full content width, edge to edge. No explicit width/max-width/align-self
   here on purpose: this is a .editable link, and that class's hitbox trick
   (padding: 0.35rem 0.5rem; margin: -0.35rem -0.5rem — see .editable
   below) already expands 0.5rem past each side of the content box and
   pulls back in with matching padding, keeping the visible text flush
   with the page's own left/right content edges. Flex items on
   .invoice-page stretch (align-items' default) by default, and stretch
   correctly resolves width as the available cross space MINUS those
   negative margins — landing the box exactly on both content edges. An
   explicit width/max-width: 100% would instead be 100% of the *positive*
   available space only, ignoring the negative margins, and fall visibly
   short of the right edge; verified against getBoundingClientRect in
   Chrome, not just assumed. overflow-wrap catches a single unbroken long
   word that would otherwise run past the page edge; text inside stays
   left-aligned (the default), never centered.

   Owner-confirmed: width stays full-page (a shrink-to-fit width was
   tried and reverted - not what was wanted). Ordered after .invoice-
   totals in the source (invoice_page.html) - OWNER CONTRACT (confirmed
   against a real invoice reference): payment text sits directly between
   totals and the footer. Height must stay content-based - see
   ".a4-page-last .a4-page-content > .invoice-payment-text" below for how
   the last-page-specific compact spacing is applied instead of here. */
.invoice-payment-text {
  margin-bottom: 1.5rem;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

.invoice-footer {
  border-top: 1px solid var(--border);
  padding-top: 0.75rem;
  font-size: 0.85rem;
  color: #333;
  margin-left: auto;
  margin-right: auto;
  max-width: 60%;
  overflow-wrap: anywhere;
  /* Each per-line <div> (settings.footer_text.splitlines(), see
     invoice_page.html) is its own block, so it needs its own
     text-align rather than relying on any single-line default - every
     line is centered symmetrically within the block's own max-width:
     60% column, not just the block itself. Applied once here on the
     base rule (not per page/clone): canonical footer, every repeated
     .a4-clone-static clone, and every #print-document clone all share
     the "invoice-footer" class (see app.js markVisualOnlyClone/
     cleanClone - clones keep the base class, never strip it), so this
     single declaration already reaches screen and real print alike,
     with no need for a duplicate page-scoped or clone-scoped rule. */
  text-align: center;
  /* margin-left/right: auto here only centers the block horizontally —
     vertical space-absorption is not this rule's job. .invoice-totals
     above already absorbs the page's leftover vertical space, carrying
     totals, payment text, and this footer down together as one group
     (OWNER CONTRACT, confirmed against a real invoice reference: totals,
     then payment text, then the footer). */
}

/* --- Dialogs (CSS-only modal via :target) --- */

.dialog-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  align-items: center;
  justify-content: center;
  z-index: 10;
  padding: 1rem;
}

.dialog-overlay:target {
  display: flex;
}

.dialog-box {
  background: #fff;
  border-radius: 8px;
  padding: 1.25rem 1.5rem;
  max-width: 30rem;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
}

.dialog-box h2 {
  margin-top: 0;
  font-size: 1.1rem;
}

.dialog-box label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.9rem;
  font-size: 0.9rem;
}

.dialog-box input[type="text"],
.dialog-box input[type="date"],
.dialog-box input[type="file"],
.dialog-box textarea {
  display: block;
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  font-size: 0.95rem;
  font-family: inherit;
  font-weight: normal;
}

.dialog-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
}

.dialog-hint {
  font-size: 0.8rem;
  color: #666;
  margin-top: 1rem;
  margin-bottom: 0;
}

.logo-preview {
  max-width: 200px;
  max-height: 120px;
  display: block;
  margin-bottom: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.25rem;
  background: #fff;
}

/* --- Settings page --- */

.settings-page {
  max-width: 700px;
  margin: 1.5rem auto;
  padding: 0 1.5rem;
}

.panel {
  border: 1px solid var(--border);
  background: var(--panel-bg);
  border-radius: 6px;
  padding: 1rem 1.25rem;
  margin-bottom: 1.25rem;
}

.panel h2 {
  font-size: 1.1rem;
  margin-top: 0;
  margin-bottom: 0.75rem;
  border-bottom: 1px solid var(--border);
  padding-bottom: 0.4rem;
}

.panel label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.9rem;
  font-size: 0.9rem;
}

.panel input[type="text"],
.panel textarea {
  display: block;
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  font-size: 0.95rem;
  font-family: inherit;
  font-weight: normal;
}
