@facturion/invoice-renderer 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export type Lang = "en" | "de";
5
5
  /** Label resolver: `(key, vars?) => string`, returning the key unchanged on a miss. */
6
6
  export type TFunction = (key: string, vars?: Record<string, unknown>) => string;
7
7
 
8
- export { lineNet, computeTotals } from "@facturion/invoice";
8
+ export { lineNet, computeTotals } from "@facturion/invoice/model";
9
9
 
10
10
  /** HTML-escape a value (`&`, `<`, `>`, `"`). */
11
11
  export function esc(s: unknown): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@facturion/invoice-renderer",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Render the @facturion/invoice simplified-JSON model to a human-readable HTML invoice — the presentation layer behind Facturion's invoice PDFs.",
5
5
  "keywords": [
6
6
  "en16931",
@@ -28,9 +28,7 @@
28
28
  "types": "./index.d.ts",
29
29
  "default": "./src/index.js"
30
30
  },
31
- "./styles/view.css": "./styles/view.css",
32
- "./styles/utilities.css": "./styles/utilities.css",
33
- "./styles/variables.css": "./styles/variables.css",
31
+ "./styles/invoice.css": "./styles/invoice.css",
34
32
  "./package.json": "./package.json"
35
33
  },
36
34
  "files": [
@@ -42,12 +40,15 @@
42
40
  "NOTICE"
43
41
  ],
44
42
  "scripts": {
43
+ "generate": "node scripts/generate-styles.mjs",
44
+ "prepare": "npm run generate",
45
+ "pretest": "npm run generate",
45
46
  "test": "node --test test/*.test.js",
46
47
  "prepublishOnly": "npm test"
47
48
  },
48
49
  "dependencies": {
49
50
  "@facturion/codelists": "^0.1.1",
50
- "@facturion/invoice": "^0.1.0"
51
+ "@facturion/invoice": "^0.1.1"
51
52
  },
52
53
  "engines": {
53
54
  "node": ">=20"
package/src/document.js CHANGED
@@ -1,17 +1,14 @@
1
1
  // Batteries-included wrapper: a standalone HTML document for an invoice, with
2
2
  // the bundled stylesheet inlined and a default label resolver. This is what an
3
3
  // HTML→PDF service (e.g. headless Chromium) consumes.
4
+ //
5
+ // The CSS is embedded as JS strings (./styles.js, generated from styles/*.css),
6
+ // so this module has no filesystem dependency and works in the browser as well
7
+ // as in Node.
4
8
 
5
- import { readFileSync } from "node:fs";
6
- import { dirname, join } from "node:path";
7
- import { fileURLToPath } from "node:url";
8
9
  import { renderInvoice } from "./render.js";
9
10
  import { makeT, DEFAULT_LANG } from "./i18n.js";
10
-
11
- const _stylesDir = join(dirname(fileURLToPath(import.meta.url)), "..", "styles");
12
- const _variablesCss = readFileSync(join(_stylesDir, "variables.css"), "utf-8");
13
- const _utilitiesCss = readFileSync(join(_stylesDir, "utilities.css"), "utf-8");
14
- const _viewCss = readFileSync(join(_stylesDir, "view.css"), "utf-8");
11
+ import { invoiceCss } from "./styles.js";
15
12
 
16
13
  const _HTML_LANG = { en: "en", de: "de" };
17
14
 
@@ -32,9 +29,7 @@ export function renderInvoiceDocument(invoice, { lang = DEFAULT_LANG, t } = {})
32
29
  <head>
33
30
  <meta charset="utf-8">
34
31
  <style>
35
- ${_variablesCss}
36
- ${_utilitiesCss}
37
- ${_viewCss}
32
+ ${invoiceCss}
38
33
  body { margin: 0; padding: 0; background: #fff; }
39
34
  </style>
40
35
  </head>
package/src/render.js CHANGED
@@ -10,7 +10,10 @@
10
10
  // and `units.*` / `paymentMeans.*` vocabulary. `renderInvoiceDocument` supplies
11
11
  // a default `t` (bundled strings + @facturion/codelists); advanced callers pass
12
12
  // their own. The net/total math comes from @facturion/invoice.
13
- import { computeTotals, lineNet } from "@facturion/invoice";
13
+ // Import from the pure ./model subpath, not the package root — the root eagerly
14
+ // compiles Ajv validators at load, which we don't want dragged into the renderer
15
+ // (and, transitively, the browser) bundle just for the net/total math.
16
+ import { computeTotals, lineNet } from "@facturion/invoice/model";
14
17
 
15
18
  export { computeTotals, lineNet };
16
19
 
package/src/styles.js ADDED
@@ -0,0 +1,4 @@
1
+ // Generated by scripts/generate-styles.mjs from styles/invoice.css.
2
+ // Do not edit by hand.
3
+
4
+ export const invoiceCss = "/* @facturion/invoice-renderer — invoice render stylesheet.\n * The traditional invoice rendering (.invoice-paper / .inv-*), self-contained:\n * design tokens are scoped to .invoice-paper (not :root) so importing this into\n * a host app neither depends on nor collides with the app's global tokens.\n * Light is the default; .invoice-paper--dark is the dark theme (preview only). */\n\n.invoice-paper {\n --bg-card: #18223a;\n --bg-elevated: #1d2742;\n --border: rgba(255, 255, 255, 0.08);\n --border-light: rgba(255, 255, 255, 0.14);\n --primary-light: #7fa0d8;\n --radius-md: 8px;\n --radius-sm: 4px;\n --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.4);\n --space-1: 0.25rem;\n --space-10: 2.5rem;\n --space-2: 0.5rem;\n --space-3: 0.75rem;\n --space-4: 1rem;\n --space-6: 1.5rem;\n --space-8: 2rem;\n --text: #e7ecf6;\n --text-base: 1rem;\n --text-muted: #647190;\n --text-secondary: #99a6bf;\n --text-sm: 0.875rem;\n --text-xl: 1.3rem;\n --text-xs: 0.8125rem;\n}\n\n/* Table base (scoped) */\n.invoice-paper .data-table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--text-sm);\n}\n\n.invoice-paper .data-table th,\n.invoice-paper .data-table td {\n text-align: left;\n padding: var(--space-2) var(--space-3);\n border-bottom: 1px solid var(--border);\n}\n\n.invoice-paper .data-table th {\n color: var(--text-secondary);\n font-weight: 500;\n font-size: var(--text-xs);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.invoice-paper .data-table td {\n color: var(--text-secondary);\n}\n\n.invoice-paper .data-table code {\n font-size: var(--text-xs);\n color: var(--primary-light);\n}\n\n/* Invoice render rules */\n.invoice-paper {\n background: #fdf8f0;\n color: #111;\n border-radius: var(--radius-md);\n padding: var(--space-10);\n box-shadow: var(--shadow-lg);\n font-size: var(--text-sm);\n line-height: 1.6;\n}\n\n.inv-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: var(--space-8);\n padding-bottom: var(--space-6);\n border-bottom: 2px solid #e2e8f0;\n}\n\n.inv-type {\n font-size: var(--text-xs);\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: #64748b;\n margin-bottom: var(--space-1);\n}\n\n.inv-id {\n font-size: var(--text-xl);\n font-weight: 700;\n color: #0f172a;\n}\n\n.inv-header-right {\n text-align: right;\n color: #475569;\n}\n\n.inv-header-right > div {\n margin-bottom: var(--space-1);\n}\n\n.inv-parties {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: var(--space-8);\n margin-bottom: var(--space-8);\n}\n\n.inv-party-label {\n font-size: var(--text-xs);\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: #94a3b8;\n margin-bottom: var(--space-2);\n}\n\n.inv-party-name {\n font-weight: 600;\n color: #0f172a;\n margin-bottom: var(--space-1);\n}\n\n.inv-party > div {\n color: #475569;\n}\n\n.inv-party-trading {\n font-style: italic;\n color: #64748b !important;\n margin-bottom: var(--space-1);\n}\n\n.inv-party-legal {\n margin-top: var(--space-2);\n font-size: var(--text-xs);\n color: #64748b !important;\n line-height: 1.4;\n}\n\n.inv-party-contact {\n margin-top: var(--space-2);\n font-size: var(--text-xs);\n color: #475569 !important;\n}\n\n.inv-parties-secondary {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));\n gap: var(--space-6);\n margin-bottom: var(--space-8);\n}\n\n.inv-references {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));\n gap: var(--space-1) var(--space-6);\n margin-bottom: var(--space-6);\n padding: var(--space-3) var(--space-4);\n background: #f5ede0;\n border-radius: var(--radius-sm);\n font-size: var(--text-xs);\n}\n\n.inv-ref {\n display: flex;\n gap: var(--space-2);\n color: #475569;\n}\n\n.inv-ref-label {\n font-weight: 600;\n letter-spacing: 0.04em;\n color: #64748b;\n text-transform: uppercase;\n flex-shrink: 0;\n}\n\n.inv-ref-value {\n color: #0f172a;\n word-break: break-all;\n}\n\n.inv-period {\n margin-bottom: var(--space-6);\n padding: var(--space-2) var(--space-4);\n background: #f5ede0;\n border-left: 3px solid #94a3b8;\n border-radius: var(--radius-sm);\n color: #334155;\n font-size: var(--text-sm);\n}\n\n.inv-notes,\n.inv-payment {\n margin-bottom: var(--space-6);\n padding: var(--space-4);\n background: #f5ede0;\n border-radius: var(--radius-sm);\n color: #334155;\n}\n\n.inv-references,\n.inv-period,\n.inv-notes,\n.inv-payment,\n.inv-payment-terms,\n.inv-totals-wrap,\n.inv-lines tr {\n break-inside: avoid;\n}\n\n.inv-notes p {\n margin: 0 0 var(--space-2) 0;\n}\n\n.inv-notes p:last-child {\n margin-bottom: 0;\n}\n\n.inv-payment > div {\n margin-bottom: var(--space-1);\n}\n\n.inv-section-label {\n font-size: var(--text-xs);\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: #64748b;\n margin-bottom: var(--space-2);\n}\n\n.inv-label {\n font-size: var(--text-xs);\n font-weight: 600;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: #94a3b8;\n margin-right: 0.3em;\n}\n\n.inv-lines-wrap {\n margin-bottom: var(--space-6);\n overflow-x: auto;\n}\n\n.inv-lines {\n color: #1e293b;\n}\n\n.inv-lines thead th {\n background: #f5ede0;\n color: #64748b;\n}\n\n.inv-col-id {\n width: 3rem;\n color: #94a3b8;\n}\n\n.inv-col-desc {\n /* takes remaining space */\n}\n\n.inv-col-num {\n text-align: right;\n white-space: nowrap;\n}\n\n.inv-line-name {\n font-weight: 500;\n color: #0f172a;\n}\n\n.inv-line-desc,\n.inv-line-meta,\n.inv-line-allowance,\n.inv-line-charge {\n font-size: var(--text-xs);\n color: #64748b;\n line-height: 1.4;\n}\n\n.inv-line-meta {\n margin-top: var(--space-1);\n}\n\n.inv-line-allowance {\n color: #b45309; /* amber-700 */\n}\n\n.inv-line-charge {\n color: #475569;\n}\n\n.inv-line-price-gross {\n font-size: var(--text-xs);\n color: #94a3b8;\n text-decoration: line-through;\n}\n\n.inv-vat-base {\n font-size: var(--text-xs);\n color: #94a3b8;\n font-weight: 400;\n}\n\n.inv-vat-reason td {\n padding-top: 0 !important;\n padding-bottom: var(--space-2) !important;\n font-size: var(--text-xs);\n font-style: italic;\n color: #64748b;\n line-height: 1.3;\n}\n\n.inv-totals-wrap {\n display: flex;\n justify-content: flex-end;\n margin-bottom: var(--space-6);\n}\n\n.inv-totals {\n min-width: 280px;\n border-collapse: collapse;\n color: #1e293b;\n}\n\n.inv-totals td {\n padding: var(--space-1) var(--space-3);\n}\n\n.inv-totals td:first-child {\n color: #475569;\n}\n\n.inv-totals td.inv-col-num {\n font-variant-numeric: tabular-nums;\n}\n\n.inv-totals tr:first-child td {\n padding-top: var(--space-3);\n border-top: 1px solid #e2e8f0;\n}\n\n.inv-totals-payable td {\n padding-top: var(--space-3);\n border-top: 2px solid #e2e8f0;\n font-weight: 700;\n font-size: var(--text-base);\n color: #0f172a;\n}\n\n.invoice-paper--dark {\n background: var(--bg-elevated);\n color: var(--text);\n}\n\n.invoice-paper--dark .inv-header {\n border-bottom-color: var(--border);\n}\n\n.invoice-paper--dark .inv-type {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-id {\n color: var(--text);\n}\n\n.invoice-paper--dark .inv-header-right {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-party-name {\n color: var(--text);\n}\n\n.invoice-paper--dark .inv-party > div {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-party-label,\n.invoice-paper--dark .inv-label {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-lines {\n color: var(--text);\n}\n\n.invoice-paper--dark .inv-lines thead th {\n background: var(--bg-card);\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .data-table td {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-col-id {\n color: var(--text-muted);\n}\n\n.invoice-paper--dark .inv-totals {\n color: var(--text);\n}\n\n.invoice-paper--dark .inv-totals td:first-child {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-totals tr:first-child td {\n border-top-color: var(--border);\n}\n\n.invoice-paper--dark .inv-totals-payable td {\n border-top-color: var(--border-light);\n color: var(--text);\n}\n\n.invoice-paper--dark .inv-payment-terms {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-party-trading,\n.invoice-paper--dark .inv-party-legal,\n.invoice-paper--dark .inv-party-contact {\n color: var(--text-secondary) !important;\n}\n\n.invoice-paper--dark .inv-references,\n.invoice-paper--dark .inv-period,\n.invoice-paper--dark .inv-notes,\n.invoice-paper--dark .inv-payment {\n background: var(--bg-card);\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-ref {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-ref-label,\n.invoice-paper--dark .inv-section-label {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-ref-value {\n color: var(--text);\n}\n\n.invoice-paper--dark .inv-period {\n border-left-color: var(--border-light);\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-line-name {\n color: var(--text);\n}\n\n.invoice-paper--dark .inv-line-desc,\n.invoice-paper--dark .inv-line-meta,\n.invoice-paper--dark .inv-line-charge {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-line-price-gross {\n color: var(--text-secondary);\n}\n\n.invoice-paper--dark .inv-vat-base,\n.invoice-paper--dark .inv-vat-reason td {\n color: var(--text-secondary);\n}\n\n@media print {\n@page {\n margin: 1.5cm;\n }\n\n.invoice-paper {\n box-shadow: none;\n border-radius: 0;\n padding: 0;\n background: #fff;\n }\n}\n\n@media (max-width: 600px) {\n.inv-parties {\n grid-template-columns: 1fr;\n }\n\n.inv-header {\n flex-direction: column;\n gap: var(--space-3);\n }\n\n.inv-header-right {\n text-align: left;\n }\n}\n";
@@ -0,0 +1,509 @@
1
+ /* @facturion/invoice-renderer — invoice render stylesheet.
2
+ * The traditional invoice rendering (.invoice-paper / .inv-*), self-contained:
3
+ * design tokens are scoped to .invoice-paper (not :root) so importing this into
4
+ * a host app neither depends on nor collides with the app's global tokens.
5
+ * Light is the default; .invoice-paper--dark is the dark theme (preview only). */
6
+
7
+ .invoice-paper {
8
+ --bg-card: #18223a;
9
+ --bg-elevated: #1d2742;
10
+ --border: rgba(255, 255, 255, 0.08);
11
+ --border-light: rgba(255, 255, 255, 0.14);
12
+ --primary-light: #7fa0d8;
13
+ --radius-md: 8px;
14
+ --radius-sm: 4px;
15
+ --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.4);
16
+ --space-1: 0.25rem;
17
+ --space-10: 2.5rem;
18
+ --space-2: 0.5rem;
19
+ --space-3: 0.75rem;
20
+ --space-4: 1rem;
21
+ --space-6: 1.5rem;
22
+ --space-8: 2rem;
23
+ --text: #e7ecf6;
24
+ --text-base: 1rem;
25
+ --text-muted: #647190;
26
+ --text-secondary: #99a6bf;
27
+ --text-sm: 0.875rem;
28
+ --text-xl: 1.3rem;
29
+ --text-xs: 0.8125rem;
30
+ }
31
+
32
+ /* Table base (scoped) */
33
+ .invoice-paper .data-table {
34
+ width: 100%;
35
+ border-collapse: collapse;
36
+ font-size: var(--text-sm);
37
+ }
38
+
39
+ .invoice-paper .data-table th,
40
+ .invoice-paper .data-table td {
41
+ text-align: left;
42
+ padding: var(--space-2) var(--space-3);
43
+ border-bottom: 1px solid var(--border);
44
+ }
45
+
46
+ .invoice-paper .data-table th {
47
+ color: var(--text-secondary);
48
+ font-weight: 500;
49
+ font-size: var(--text-xs);
50
+ text-transform: uppercase;
51
+ letter-spacing: 0.04em;
52
+ }
53
+
54
+ .invoice-paper .data-table td {
55
+ color: var(--text-secondary);
56
+ }
57
+
58
+ .invoice-paper .data-table code {
59
+ font-size: var(--text-xs);
60
+ color: var(--primary-light);
61
+ }
62
+
63
+ /* Invoice render rules */
64
+ .invoice-paper {
65
+ background: #fdf8f0;
66
+ color: #111;
67
+ border-radius: var(--radius-md);
68
+ padding: var(--space-10);
69
+ box-shadow: var(--shadow-lg);
70
+ font-size: var(--text-sm);
71
+ line-height: 1.6;
72
+ }
73
+
74
+ .inv-header {
75
+ display: flex;
76
+ justify-content: space-between;
77
+ align-items: flex-start;
78
+ margin-bottom: var(--space-8);
79
+ padding-bottom: var(--space-6);
80
+ border-bottom: 2px solid #e2e8f0;
81
+ }
82
+
83
+ .inv-type {
84
+ font-size: var(--text-xs);
85
+ font-weight: 700;
86
+ letter-spacing: 0.1em;
87
+ text-transform: uppercase;
88
+ color: #64748b;
89
+ margin-bottom: var(--space-1);
90
+ }
91
+
92
+ .inv-id {
93
+ font-size: var(--text-xl);
94
+ font-weight: 700;
95
+ color: #0f172a;
96
+ }
97
+
98
+ .inv-header-right {
99
+ text-align: right;
100
+ color: #475569;
101
+ }
102
+
103
+ .inv-header-right > div {
104
+ margin-bottom: var(--space-1);
105
+ }
106
+
107
+ .inv-parties {
108
+ display: grid;
109
+ grid-template-columns: 1fr 1fr;
110
+ gap: var(--space-8);
111
+ margin-bottom: var(--space-8);
112
+ }
113
+
114
+ .inv-party-label {
115
+ font-size: var(--text-xs);
116
+ font-weight: 700;
117
+ letter-spacing: 0.08em;
118
+ text-transform: uppercase;
119
+ color: #94a3b8;
120
+ margin-bottom: var(--space-2);
121
+ }
122
+
123
+ .inv-party-name {
124
+ font-weight: 600;
125
+ color: #0f172a;
126
+ margin-bottom: var(--space-1);
127
+ }
128
+
129
+ .inv-party > div {
130
+ color: #475569;
131
+ }
132
+
133
+ .inv-party-trading {
134
+ font-style: italic;
135
+ color: #64748b !important;
136
+ margin-bottom: var(--space-1);
137
+ }
138
+
139
+ .inv-party-legal {
140
+ margin-top: var(--space-2);
141
+ font-size: var(--text-xs);
142
+ color: #64748b !important;
143
+ line-height: 1.4;
144
+ }
145
+
146
+ .inv-party-contact {
147
+ margin-top: var(--space-2);
148
+ font-size: var(--text-xs);
149
+ color: #475569 !important;
150
+ }
151
+
152
+ .inv-parties-secondary {
153
+ display: grid;
154
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
155
+ gap: var(--space-6);
156
+ margin-bottom: var(--space-8);
157
+ }
158
+
159
+ .inv-references {
160
+ display: grid;
161
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
162
+ gap: var(--space-1) var(--space-6);
163
+ margin-bottom: var(--space-6);
164
+ padding: var(--space-3) var(--space-4);
165
+ background: #f5ede0;
166
+ border-radius: var(--radius-sm);
167
+ font-size: var(--text-xs);
168
+ }
169
+
170
+ .inv-ref {
171
+ display: flex;
172
+ gap: var(--space-2);
173
+ color: #475569;
174
+ }
175
+
176
+ .inv-ref-label {
177
+ font-weight: 600;
178
+ letter-spacing: 0.04em;
179
+ color: #64748b;
180
+ text-transform: uppercase;
181
+ flex-shrink: 0;
182
+ }
183
+
184
+ .inv-ref-value {
185
+ color: #0f172a;
186
+ word-break: break-all;
187
+ }
188
+
189
+ .inv-period {
190
+ margin-bottom: var(--space-6);
191
+ padding: var(--space-2) var(--space-4);
192
+ background: #f5ede0;
193
+ border-left: 3px solid #94a3b8;
194
+ border-radius: var(--radius-sm);
195
+ color: #334155;
196
+ font-size: var(--text-sm);
197
+ }
198
+
199
+ .inv-notes,
200
+ .inv-payment {
201
+ margin-bottom: var(--space-6);
202
+ padding: var(--space-4);
203
+ background: #f5ede0;
204
+ border-radius: var(--radius-sm);
205
+ color: #334155;
206
+ }
207
+
208
+ .inv-references,
209
+ .inv-period,
210
+ .inv-notes,
211
+ .inv-payment,
212
+ .inv-payment-terms,
213
+ .inv-totals-wrap,
214
+ .inv-lines tr {
215
+ break-inside: avoid;
216
+ }
217
+
218
+ .inv-notes p {
219
+ margin: 0 0 var(--space-2) 0;
220
+ }
221
+
222
+ .inv-notes p:last-child {
223
+ margin-bottom: 0;
224
+ }
225
+
226
+ .inv-payment > div {
227
+ margin-bottom: var(--space-1);
228
+ }
229
+
230
+ .inv-section-label {
231
+ font-size: var(--text-xs);
232
+ font-weight: 700;
233
+ letter-spacing: 0.08em;
234
+ text-transform: uppercase;
235
+ color: #64748b;
236
+ margin-bottom: var(--space-2);
237
+ }
238
+
239
+ .inv-label {
240
+ font-size: var(--text-xs);
241
+ font-weight: 600;
242
+ letter-spacing: 0.04em;
243
+ text-transform: uppercase;
244
+ color: #94a3b8;
245
+ margin-right: 0.3em;
246
+ }
247
+
248
+ .inv-lines-wrap {
249
+ margin-bottom: var(--space-6);
250
+ overflow-x: auto;
251
+ }
252
+
253
+ .inv-lines {
254
+ color: #1e293b;
255
+ }
256
+
257
+ .inv-lines thead th {
258
+ background: #f5ede0;
259
+ color: #64748b;
260
+ }
261
+
262
+ .inv-col-id {
263
+ width: 3rem;
264
+ color: #94a3b8;
265
+ }
266
+
267
+ .inv-col-desc {
268
+ /* takes remaining space */
269
+ }
270
+
271
+ .inv-col-num {
272
+ text-align: right;
273
+ white-space: nowrap;
274
+ }
275
+
276
+ .inv-line-name {
277
+ font-weight: 500;
278
+ color: #0f172a;
279
+ }
280
+
281
+ .inv-line-desc,
282
+ .inv-line-meta,
283
+ .inv-line-allowance,
284
+ .inv-line-charge {
285
+ font-size: var(--text-xs);
286
+ color: #64748b;
287
+ line-height: 1.4;
288
+ }
289
+
290
+ .inv-line-meta {
291
+ margin-top: var(--space-1);
292
+ }
293
+
294
+ .inv-line-allowance {
295
+ color: #b45309; /* amber-700 */
296
+ }
297
+
298
+ .inv-line-charge {
299
+ color: #475569;
300
+ }
301
+
302
+ .inv-line-price-gross {
303
+ font-size: var(--text-xs);
304
+ color: #94a3b8;
305
+ text-decoration: line-through;
306
+ }
307
+
308
+ .inv-vat-base {
309
+ font-size: var(--text-xs);
310
+ color: #94a3b8;
311
+ font-weight: 400;
312
+ }
313
+
314
+ .inv-vat-reason td {
315
+ padding-top: 0 !important;
316
+ padding-bottom: var(--space-2) !important;
317
+ font-size: var(--text-xs);
318
+ font-style: italic;
319
+ color: #64748b;
320
+ line-height: 1.3;
321
+ }
322
+
323
+ .inv-totals-wrap {
324
+ display: flex;
325
+ justify-content: flex-end;
326
+ margin-bottom: var(--space-6);
327
+ }
328
+
329
+ .inv-totals {
330
+ min-width: 280px;
331
+ border-collapse: collapse;
332
+ color: #1e293b;
333
+ }
334
+
335
+ .inv-totals td {
336
+ padding: var(--space-1) var(--space-3);
337
+ }
338
+
339
+ .inv-totals td:first-child {
340
+ color: #475569;
341
+ }
342
+
343
+ .inv-totals td.inv-col-num {
344
+ font-variant-numeric: tabular-nums;
345
+ }
346
+
347
+ .inv-totals tr:first-child td {
348
+ padding-top: var(--space-3);
349
+ border-top: 1px solid #e2e8f0;
350
+ }
351
+
352
+ .inv-totals-payable td {
353
+ padding-top: var(--space-3);
354
+ border-top: 2px solid #e2e8f0;
355
+ font-weight: 700;
356
+ font-size: var(--text-base);
357
+ color: #0f172a;
358
+ }
359
+
360
+ .invoice-paper--dark {
361
+ background: var(--bg-elevated);
362
+ color: var(--text);
363
+ }
364
+
365
+ .invoice-paper--dark .inv-header {
366
+ border-bottom-color: var(--border);
367
+ }
368
+
369
+ .invoice-paper--dark .inv-type {
370
+ color: var(--text-secondary);
371
+ }
372
+
373
+ .invoice-paper--dark .inv-id {
374
+ color: var(--text);
375
+ }
376
+
377
+ .invoice-paper--dark .inv-header-right {
378
+ color: var(--text-secondary);
379
+ }
380
+
381
+ .invoice-paper--dark .inv-party-name {
382
+ color: var(--text);
383
+ }
384
+
385
+ .invoice-paper--dark .inv-party > div {
386
+ color: var(--text-secondary);
387
+ }
388
+
389
+ .invoice-paper--dark .inv-party-label,
390
+ .invoice-paper--dark .inv-label {
391
+ color: var(--text-secondary);
392
+ }
393
+
394
+ .invoice-paper--dark .inv-lines {
395
+ color: var(--text);
396
+ }
397
+
398
+ .invoice-paper--dark .inv-lines thead th {
399
+ background: var(--bg-card);
400
+ color: var(--text-secondary);
401
+ }
402
+
403
+ .invoice-paper--dark .data-table td {
404
+ color: var(--text-secondary);
405
+ }
406
+
407
+ .invoice-paper--dark .inv-col-id {
408
+ color: var(--text-muted);
409
+ }
410
+
411
+ .invoice-paper--dark .inv-totals {
412
+ color: var(--text);
413
+ }
414
+
415
+ .invoice-paper--dark .inv-totals td:first-child {
416
+ color: var(--text-secondary);
417
+ }
418
+
419
+ .invoice-paper--dark .inv-totals tr:first-child td {
420
+ border-top-color: var(--border);
421
+ }
422
+
423
+ .invoice-paper--dark .inv-totals-payable td {
424
+ border-top-color: var(--border-light);
425
+ color: var(--text);
426
+ }
427
+
428
+ .invoice-paper--dark .inv-payment-terms {
429
+ color: var(--text-secondary);
430
+ }
431
+
432
+ .invoice-paper--dark .inv-party-trading,
433
+ .invoice-paper--dark .inv-party-legal,
434
+ .invoice-paper--dark .inv-party-contact {
435
+ color: var(--text-secondary) !important;
436
+ }
437
+
438
+ .invoice-paper--dark .inv-references,
439
+ .invoice-paper--dark .inv-period,
440
+ .invoice-paper--dark .inv-notes,
441
+ .invoice-paper--dark .inv-payment {
442
+ background: var(--bg-card);
443
+ color: var(--text-secondary);
444
+ }
445
+
446
+ .invoice-paper--dark .inv-ref {
447
+ color: var(--text-secondary);
448
+ }
449
+
450
+ .invoice-paper--dark .inv-ref-label,
451
+ .invoice-paper--dark .inv-section-label {
452
+ color: var(--text-secondary);
453
+ }
454
+
455
+ .invoice-paper--dark .inv-ref-value {
456
+ color: var(--text);
457
+ }
458
+
459
+ .invoice-paper--dark .inv-period {
460
+ border-left-color: var(--border-light);
461
+ color: var(--text-secondary);
462
+ }
463
+
464
+ .invoice-paper--dark .inv-line-name {
465
+ color: var(--text);
466
+ }
467
+
468
+ .invoice-paper--dark .inv-line-desc,
469
+ .invoice-paper--dark .inv-line-meta,
470
+ .invoice-paper--dark .inv-line-charge {
471
+ color: var(--text-secondary);
472
+ }
473
+
474
+ .invoice-paper--dark .inv-line-price-gross {
475
+ color: var(--text-secondary);
476
+ }
477
+
478
+ .invoice-paper--dark .inv-vat-base,
479
+ .invoice-paper--dark .inv-vat-reason td {
480
+ color: var(--text-secondary);
481
+ }
482
+
483
+ @media print {
484
+ @page {
485
+ margin: 1.5cm;
486
+ }
487
+
488
+ .invoice-paper {
489
+ box-shadow: none;
490
+ border-radius: 0;
491
+ padding: 0;
492
+ background: #fff;
493
+ }
494
+ }
495
+
496
+ @media (max-width: 600px) {
497
+ .inv-parties {
498
+ grid-template-columns: 1fr;
499
+ }
500
+
501
+ .inv-header {
502
+ flex-direction: column;
503
+ gap: var(--space-3);
504
+ }
505
+
506
+ .inv-header-right {
507
+ text-align: left;
508
+ }
509
+ }