@inherent.design/brand 0.3.1 → 0.4.1
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/LICENSE +202 -0
- package/fonts/dist/en/279406bfba1f0020d1fce5ec6fd2a735.woff2 +0 -0
- package/fonts/dist/en/41252d539ae6f30ba7b2f91398c8f31d.woff2 +0 -0
- package/fonts/dist/en/5228f68218619155166c83a4ee524afe.woff2 +0 -0
- package/fonts/dist/en/98773bdef5c210140ab004df6c5a8331.woff2 +0 -0
- package/fonts/dist/en/c5b99cd7f72716cad8b79f5452bc2f37.woff2 +0 -0
- package/fonts/dist/en/d88a44b1521929f0cedd8f54f815b90e.woff2 +0 -0
- package/fonts/dist/en/ffb5f092abefd125a087b64efb40c9b0.woff2 +0 -0
- package/fonts/dist/en/index.css +9 -9
- package/fonts/dist/hi/index.css +3 -3
- package/fonts/dist/zh/16144bae757cf69e1303657f4666167b.woff2 +0 -0
- package/fonts/dist/zh/da5d9643f9cd74d319fb3c3fcf47b73c.woff2 +0 -0
- package/fonts/dist/zh/e00f6f95a6a747bfbd32af5df1fe1f1c.woff2 +0 -0
- package/fonts/dist/zh/index.css +4 -4
- package/package.json +8 -4
- package/typst/inherent.typ +22 -0
- package/typst/lib/code.typ +24 -0
- package/typst/lib/colors.typ +22 -0
- package/typst/lib/components-content.typ +161 -0
- package/typst/lib/components-document.typ +275 -0
- package/typst/lib/components-layout.typ +104 -0
- package/typst/lib/components.typ +55 -0
- package/typst/lib/layout.typ +35 -0
- package/typst/lib/tables.typ +19 -0
- package/typst/lib/tokens.typ +54 -0
- package/typst/lib/typography.typ +58 -0
- package/typst/lib/utils.typ +55 -0
- package/typst/prelude.typ +6 -0
- package/typst/typst.toml +7 -0
- package/fonts/dist/en/1c6a77e998e54efef01b7caf56bc42a3.woff2 +0 -0
- package/fonts/dist/en/4db369d2bd069bba9eecb25938906a84.woff2 +0 -0
- package/fonts/dist/en/50be83d8450ee117be0e081216cb6c61.woff2 +0 -0
- package/fonts/dist/en/6543456df4504eb8eec3cb90d97fd006.woff2 +0 -0
- package/fonts/dist/en/91424d2ed0640f376b7ab256afefe512.woff2 +0 -0
- package/fonts/dist/en/bd7f480f2bcf174edd41ec205c345a62.woff2 +0 -0
- package/fonts/dist/en/e5fe10dc949fa944db6496bcd735f594.woff2 +0 -0
- package/fonts/dist/zh/67cdd35800ba1cfd8e98b3943bd77834.woff2 +0 -0
- package/fonts/dist/zh/6ee817625aeeadd71f081cc671e0c26e.woff2 +0 -0
- package/fonts/dist/zh/7ff3a53513ebbbc16b0f1baf70713e17.woff2 +0 -0
- /package/{license → fonts/licenses}/LICENSE-Charter.txt +0 -0
- /package/{license → fonts/licenses}/LICENSE-CommitMono.txt +0 -0
- /package/{license → fonts/licenses}/LICENSE-CormorantGaramond.txt +0 -0
- /package/{license → fonts/licenses}/LICENSE-Inter.txt +0 -0
- /package/{license → fonts/licenses}/LICENSE-LXGWWenKai.txt +0 -0
- /package/{license → fonts/licenses}/LICENSE-NotoSansDevanagari.txt +0 -0
- /package/{license → fonts/licenses}/LICENSE-NotoSansMono.txt +0 -0
- /package/{license → fonts/licenses}/LICENSE-NotoSansSC.txt +0 -0
- /package/{license → fonts/licenses}/LICENSE-SarasaMonoSC.txt +0 -0
- /package/{license → fonts/licenses}/LICENSE-TiroDevanagari.txt +0 -0
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inherent.design/brand",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Design tokens, web fonts, and Starwind components for inherent.design projects.",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
7
|
"author": "inherent.design",
|
|
8
8
|
"exports": {
|
|
9
9
|
"./fonts/vars": {
|
|
@@ -33,14 +33,18 @@
|
|
|
33
33
|
"./components/button": "./components/button/index.ts",
|
|
34
34
|
"./components/image": "./components/image/index.ts",
|
|
35
35
|
"./components/prose": "./components/prose/index.ts",
|
|
36
|
-
"./components/separator": "./components/separator/index.ts"
|
|
36
|
+
"./components/separator": "./components/separator/index.ts",
|
|
37
|
+
"./typst": "./typst/inherent.typ",
|
|
38
|
+
"./typst/prelude": "./typst/prelude.typ"
|
|
37
39
|
},
|
|
38
40
|
"files": [
|
|
39
41
|
"fonts/vars.css",
|
|
40
42
|
"fonts/dist/",
|
|
43
|
+
"fonts/src/typst/",
|
|
44
|
+
"fonts/licenses/LICENSE*",
|
|
41
45
|
"styles/",
|
|
42
46
|
"components/",
|
|
43
|
-
"
|
|
47
|
+
"typst/"
|
|
44
48
|
],
|
|
45
49
|
"scripts": {
|
|
46
50
|
"fonts:download": "node fonts/scripts/download.js",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// brand/typst/inherent.typ: Base template for inherent.design documents
|
|
2
|
+
//
|
|
3
|
+
// Pure Typst entry point. Templates use: #show: base-template.with(title: ..., date: ...)
|
|
4
|
+
|
|
5
|
+
#import "lib/colors.typ": *
|
|
6
|
+
#import "lib/tokens.typ": *
|
|
7
|
+
#import "lib/typography.typ": apply-typography
|
|
8
|
+
#import "lib/layout.typ": apply-layout
|
|
9
|
+
#import "lib/tables.typ": apply-tables
|
|
10
|
+
#import "lib/code.typ": apply-code
|
|
11
|
+
|
|
12
|
+
#let base-template(
|
|
13
|
+
title: "",
|
|
14
|
+
date: none,
|
|
15
|
+
body,
|
|
16
|
+
) = {
|
|
17
|
+
show: apply-typography
|
|
18
|
+
show: apply-layout.with(title: title, date: date)
|
|
19
|
+
show: apply-tables
|
|
20
|
+
show: apply-code
|
|
21
|
+
body
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Code/raw block styling
|
|
2
|
+
|
|
3
|
+
#import "colors.typ": accent, text-gray, bg-subtle
|
|
4
|
+
#import "typography.typ": font-code
|
|
5
|
+
#import "tokens.typ": border-thin, radius-md
|
|
6
|
+
|
|
7
|
+
#let apply-code(doc) = {
|
|
8
|
+
show raw: set text(font: font-code, size: 0.9em)
|
|
9
|
+
|
|
10
|
+
show raw.where(block: true): it => {
|
|
11
|
+
block(
|
|
12
|
+
width: 100%,
|
|
13
|
+
fill: bg-subtle,
|
|
14
|
+
stroke: border-thin + text-gray,
|
|
15
|
+
inset: 10pt,
|
|
16
|
+
radius: radius-md,
|
|
17
|
+
it,
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
show raw.where(block: false): set text(fill: accent)
|
|
22
|
+
|
|
23
|
+
doc
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Brand color tokens for inherent.design
|
|
2
|
+
// All document modules import from this file.
|
|
3
|
+
|
|
4
|
+
#let text-dark = rgb("#1a1a1a") // CSS: oklch(21.78% 0 0)
|
|
5
|
+
#let text-gray = rgb("#6b7280") // CSS: oklch(55.10% 0.0234 264.4)
|
|
6
|
+
#let accent = rgb("#2563eb") // CSS: oklch(54.61% 0.2152 262.9)
|
|
7
|
+
|
|
8
|
+
#let accent-light = accent.lighten(80%)
|
|
9
|
+
#let accent-dark = accent.darken(20%)
|
|
10
|
+
|
|
11
|
+
// Semantic tokens
|
|
12
|
+
#let success = rgb("#2E7D32")
|
|
13
|
+
#let warning = rgb("#F57F17")
|
|
14
|
+
#let error = rgb("#C62828")
|
|
15
|
+
#let info = rgb("#1565C0")
|
|
16
|
+
|
|
17
|
+
// Background variants
|
|
18
|
+
#let bg-subtle = rgb("#F8F9FA")
|
|
19
|
+
#let bg-card = rgb("#FFFFFF")
|
|
20
|
+
|
|
21
|
+
// Border
|
|
22
|
+
#let border = rgb("#DEE2E6")
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Content components: metric-card, service-card, competency-block, callout-box, pull-quote
|
|
2
|
+
|
|
3
|
+
#import "tokens.typ": sp-xs, sp-sm, sp-md, sp-lg, sp-xl, ts-h1, ts-h2, ts-h3, ts-h4, ts-body, ts-caption, border-thin, radius-md
|
|
4
|
+
#import "colors.typ": accent, text-dark, text-gray, bg-subtle, success, warning, error, info
|
|
5
|
+
|
|
6
|
+
#let metric-card(
|
|
7
|
+
value,
|
|
8
|
+
label,
|
|
9
|
+
description: none,
|
|
10
|
+
fill: accent.lighten(92%),
|
|
11
|
+
value-color: accent,
|
|
12
|
+
) = {
|
|
13
|
+
block(
|
|
14
|
+
width: 100%,
|
|
15
|
+
fill: fill,
|
|
16
|
+
radius: radius-md,
|
|
17
|
+
inset: sp-md,
|
|
18
|
+
)[
|
|
19
|
+
#text(size: ts-h1, weight: "bold", fill: value-color)[#value]
|
|
20
|
+
#v(sp-xs)
|
|
21
|
+
#text(size: ts-h4, weight: "semibold", fill: text-dark)[#label]
|
|
22
|
+
#if description != none {
|
|
23
|
+
v(sp-xs)
|
|
24
|
+
text(size: ts-caption, fill: text-gray)[#description]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#let service-card(
|
|
30
|
+
name,
|
|
31
|
+
description,
|
|
32
|
+
deliverables: (),
|
|
33
|
+
accent-color: accent,
|
|
34
|
+
) = {
|
|
35
|
+
block(
|
|
36
|
+
width: 100%,
|
|
37
|
+
stroke: (left: 3pt + accent-color, rest: border-thin + text-gray.lighten(70%)),
|
|
38
|
+
radius: (right: radius-md),
|
|
39
|
+
inset: sp-md,
|
|
40
|
+
below: sp-md,
|
|
41
|
+
)[
|
|
42
|
+
#text(size: ts-h3, weight: "bold", fill: accent-color)[#name]
|
|
43
|
+
#v(sp-sm)
|
|
44
|
+
#text(fill: text-dark)[#description]
|
|
45
|
+
#if deliverables.len() > 0 {
|
|
46
|
+
v(sp-sm)
|
|
47
|
+
text(size: ts-caption, weight: "bold", fill: text-gray)[Deliverables:]
|
|
48
|
+
for item in deliverables {
|
|
49
|
+
v(sp-xs)
|
|
50
|
+
grid(
|
|
51
|
+
columns: (12pt, 1fr),
|
|
52
|
+
column-gutter: sp-xs,
|
|
53
|
+
text(fill: accent-color)[--],
|
|
54
|
+
text(size: ts-caption)[#item],
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#let competency-block(
|
|
62
|
+
title,
|
|
63
|
+
items,
|
|
64
|
+
accent-color: accent,
|
|
65
|
+
columns: 2,
|
|
66
|
+
) = {
|
|
67
|
+
block(below: sp-lg)[
|
|
68
|
+
#text(size: ts-h3, weight: "bold", fill: accent-color)[#title]
|
|
69
|
+
#v(sp-sm)
|
|
70
|
+
#grid(
|
|
71
|
+
columns: (1fr,) * columns,
|
|
72
|
+
column-gutter: sp-lg,
|
|
73
|
+
row-gutter: sp-sm,
|
|
74
|
+
..items.map(item => {
|
|
75
|
+
grid(
|
|
76
|
+
columns: (8pt, 1fr),
|
|
77
|
+
column-gutter: sp-xs,
|
|
78
|
+
align(horizon, circle(radius: 3pt, fill: accent-color)),
|
|
79
|
+
text(size: ts-body, fill: text-dark)[#item],
|
|
80
|
+
)
|
|
81
|
+
}),
|
|
82
|
+
)
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#let callout-box(
|
|
87
|
+
body,
|
|
88
|
+
title: none,
|
|
89
|
+
variant: "info",
|
|
90
|
+
icon: none,
|
|
91
|
+
) = {
|
|
92
|
+
let (fill, stroke-color, title-color) = if variant == "info" {
|
|
93
|
+
(info.lighten(90%), info, info)
|
|
94
|
+
} else if variant == "warning" {
|
|
95
|
+
(warning.lighten(88%), warning, warning)
|
|
96
|
+
} else if variant == "success" {
|
|
97
|
+
(success.lighten(90%), success, success)
|
|
98
|
+
} else if variant == "error" {
|
|
99
|
+
(error.lighten(90%), error, error)
|
|
100
|
+
} else {
|
|
101
|
+
(bg-subtle, text-gray, text-dark)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
block(
|
|
105
|
+
width: 100%,
|
|
106
|
+
fill: fill,
|
|
107
|
+
stroke: (left: 3pt + stroke-color, rest: none),
|
|
108
|
+
radius: (right: radius-md),
|
|
109
|
+
inset: sp-md,
|
|
110
|
+
above: sp-md,
|
|
111
|
+
below: sp-md,
|
|
112
|
+
)[
|
|
113
|
+
#if title != none {
|
|
114
|
+
text(weight: "bold", fill: title-color, size: ts-body)[
|
|
115
|
+
#if icon != none [#icon ] #title
|
|
116
|
+
]
|
|
117
|
+
v(sp-xs)
|
|
118
|
+
}
|
|
119
|
+
#body
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Preset variants via .with()
|
|
124
|
+
#let info-box = callout-box.with(variant: "info")
|
|
125
|
+
#let warning-box = callout-box.with(variant: "warning")
|
|
126
|
+
#let success-box = callout-box.with(variant: "success")
|
|
127
|
+
#let error-box = callout-box.with(variant: "error")
|
|
128
|
+
#let note-box = callout-box.with(variant: "note", title: "Note")
|
|
129
|
+
#let tip-box = callout-box.with(variant: "info", title: "Tip")
|
|
130
|
+
#let important-box = callout-box.with(variant: "warning", title: "Important")
|
|
131
|
+
|
|
132
|
+
#let pull-quote(
|
|
133
|
+
body,
|
|
134
|
+
attribution: none,
|
|
135
|
+
accent-color: accent,
|
|
136
|
+
) = {
|
|
137
|
+
block(
|
|
138
|
+
width: 100%,
|
|
139
|
+
inset: (left: sp-xl, right: sp-lg, y: sp-lg),
|
|
140
|
+
above: sp-lg,
|
|
141
|
+
below: sp-lg,
|
|
142
|
+
)[
|
|
143
|
+
#block(
|
|
144
|
+
stroke: (left: 3pt + accent-color),
|
|
145
|
+
inset: (left: sp-md),
|
|
146
|
+
)[
|
|
147
|
+
#text(
|
|
148
|
+
size: ts-h2,
|
|
149
|
+
style: "italic",
|
|
150
|
+
fill: text-dark,
|
|
151
|
+
weight: "regular",
|
|
152
|
+
)[#body]
|
|
153
|
+
]
|
|
154
|
+
#if attribution != none {
|
|
155
|
+
v(sp-sm)
|
|
156
|
+
align(right)[
|
|
157
|
+
#text(size: ts-body, fill: text-gray)[--- #attribution]
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Document components: cover-page, contact-strip, registration-block, invoice-header, line-items-table
|
|
2
|
+
|
|
3
|
+
#import "tokens.typ": sp-xs, sp-sm, sp-md, sp-lg, sp-xl, sp-2xl, sp-3xl, ts-display, ts-h1, ts-h2, ts-body, ts-small, ts-caption, border-thin, border-medium, border-thick, radius-md
|
|
4
|
+
#import "colors.typ": accent, text-dark, text-gray, bg-subtle
|
|
5
|
+
#import "typography.typ": font-display
|
|
6
|
+
|
|
7
|
+
#let cover-page(
|
|
8
|
+
title: [],
|
|
9
|
+
subtitle: none,
|
|
10
|
+
date: none,
|
|
11
|
+
company: none,
|
|
12
|
+
fill: white,
|
|
13
|
+
accent-color: accent,
|
|
14
|
+
) = {
|
|
15
|
+
page(
|
|
16
|
+
margin: (x: 1.5in, top: 2.5in, bottom: 1.5in),
|
|
17
|
+
header: none,
|
|
18
|
+
footer: none,
|
|
19
|
+
fill: fill,
|
|
20
|
+
)[
|
|
21
|
+
// Accent bar at top
|
|
22
|
+
#place(top + left, dx: -1.5in, dy: -2.5in)[
|
|
23
|
+
#rect(width: 100% + 3in, height: 6pt, fill: accent-color)
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
// Title block
|
|
27
|
+
#text(
|
|
28
|
+
font: font-display,
|
|
29
|
+
size: ts-display + 8pt,
|
|
30
|
+
weight: "bold",
|
|
31
|
+
fill: text-dark,
|
|
32
|
+
)[#title]
|
|
33
|
+
|
|
34
|
+
#if subtitle != none {
|
|
35
|
+
v(sp-md)
|
|
36
|
+
text(size: ts-h2, fill: text-gray)[#subtitle]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#v(sp-xl)
|
|
40
|
+
#line(length: 100pt, stroke: border-thick + accent-color)
|
|
41
|
+
|
|
42
|
+
#if date != none {
|
|
43
|
+
v(sp-xl)
|
|
44
|
+
text(size: ts-body, fill: text-gray)[#date]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Company name at bottom
|
|
48
|
+
#if company != none {
|
|
49
|
+
align(bottom + left)[
|
|
50
|
+
#text(size: 12pt, fill: text-gray)[#company]
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#let contact-strip(
|
|
57
|
+
name: none,
|
|
58
|
+
title: none,
|
|
59
|
+
email: none,
|
|
60
|
+
phone: none,
|
|
61
|
+
address: none,
|
|
62
|
+
separator: [ | ],
|
|
63
|
+
) = {
|
|
64
|
+
block(
|
|
65
|
+
width: 100%,
|
|
66
|
+
inset: (y: sp-sm),
|
|
67
|
+
)[
|
|
68
|
+
#set text(size: ts-small, fill: text-gray)
|
|
69
|
+
#{
|
|
70
|
+
let parts = ()
|
|
71
|
+
if name != none { parts.push(text(weight: "bold", fill: text-dark)[#name]) }
|
|
72
|
+
if title != none { parts.push(title) }
|
|
73
|
+
if email != none { parts.push(link("mailto:" + email)[#email]) }
|
|
74
|
+
if phone != none { parts.push(phone) }
|
|
75
|
+
if address != none { parts.push(address) }
|
|
76
|
+
parts.join(separator)
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#let registration-block(
|
|
82
|
+
uei: none,
|
|
83
|
+
cage: none,
|
|
84
|
+
sam: none,
|
|
85
|
+
naics-primary: none,
|
|
86
|
+
naics-secondary: none,
|
|
87
|
+
certifications: none,
|
|
88
|
+
ein-last4: none,
|
|
89
|
+
) = {
|
|
90
|
+
block(
|
|
91
|
+
width: 100%,
|
|
92
|
+
fill: bg-subtle,
|
|
93
|
+
radius: radius-md,
|
|
94
|
+
inset: sp-md,
|
|
95
|
+
below: sp-md,
|
|
96
|
+
)[
|
|
97
|
+
#text(size: 11pt, weight: "bold", fill: accent)[Federal Registration]
|
|
98
|
+
#v(sp-sm)
|
|
99
|
+
#grid(
|
|
100
|
+
columns: (auto, 1fr),
|
|
101
|
+
row-gutter: sp-sm,
|
|
102
|
+
column-gutter: sp-md,
|
|
103
|
+
..{
|
|
104
|
+
let pairs = ()
|
|
105
|
+
if uei != none { pairs.push((strong[UEI:], uei)) }
|
|
106
|
+
if cage != none { pairs.push((strong[CAGE Code:], cage)) }
|
|
107
|
+
if sam != none { pairs.push((strong[SAM.gov:], sam)) }
|
|
108
|
+
if naics-primary != none { pairs.push((strong[NAICS (Primary):], naics-primary)) }
|
|
109
|
+
if naics-secondary != none { pairs.push((strong[NAICS (Secondary):], naics-secondary)) }
|
|
110
|
+
if certifications != none { pairs.push((strong[Certifications:], certifications)) }
|
|
111
|
+
if ein-last4 != none { pairs.push((strong[EIN (last 4):], ein-last4)) }
|
|
112
|
+
pairs.flatten()
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#let invoice-header(
|
|
119
|
+
company: none,
|
|
120
|
+
client: none,
|
|
121
|
+
number: none,
|
|
122
|
+
date: none,
|
|
123
|
+
due: none,
|
|
124
|
+
po-number: none,
|
|
125
|
+
) = {
|
|
126
|
+
grid(
|
|
127
|
+
columns: (1fr, auto),
|
|
128
|
+
column-gutter: sp-xl,
|
|
129
|
+
|
|
130
|
+
// Left: company identity
|
|
131
|
+
{
|
|
132
|
+
if company != none {
|
|
133
|
+
text(size: 14pt, weight: "bold", fill: accent)[#company.at("name", default: "")]
|
|
134
|
+
v(sp-xs)
|
|
135
|
+
if "address" in company {
|
|
136
|
+
text(size: ts-small, fill: text-gray)[#company.address]
|
|
137
|
+
}
|
|
138
|
+
if "email" in company {
|
|
139
|
+
v(sp-xs)
|
|
140
|
+
text(size: ts-small, fill: text-gray)[#company.email]
|
|
141
|
+
}
|
|
142
|
+
if "phone" in company {
|
|
143
|
+
v(sp-xs)
|
|
144
|
+
text(size: ts-small, fill: text-gray)[#company.phone]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// Right: invoice metadata
|
|
150
|
+
{
|
|
151
|
+
text(size: ts-h1, weight: "bold", fill: accent)[INVOICE]
|
|
152
|
+
v(sp-sm)
|
|
153
|
+
grid(
|
|
154
|
+
columns: (auto, auto),
|
|
155
|
+
row-gutter: sp-xs,
|
|
156
|
+
column-gutter: sp-md,
|
|
157
|
+
text(size: ts-small, weight: "bold")[Invoice \#:], text(size: ts-small)[#number],
|
|
158
|
+
text(size: ts-small, weight: "bold")[Date:], text(size: ts-small)[#date],
|
|
159
|
+
text(size: ts-small, weight: "bold")[Due:], text(size: ts-small)[#due],
|
|
160
|
+
..if po-number != none {
|
|
161
|
+
(text(size: ts-small, weight: "bold")[PO \#:], text(size: ts-small)[#po-number])
|
|
162
|
+
} else { () },
|
|
163
|
+
)
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
v(sp-lg)
|
|
168
|
+
|
|
169
|
+
// Client: "Bill To" section
|
|
170
|
+
if client != none {
|
|
171
|
+
text(size: ts-small, weight: "bold", fill: text-gray)[BILL TO]
|
|
172
|
+
v(sp-xs)
|
|
173
|
+
text(size: ts-body)[#client.at("name", default: "")]
|
|
174
|
+
if "address" in client {
|
|
175
|
+
v(sp-xs)
|
|
176
|
+
text(size: ts-small, fill: text-gray)[#client.address]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
v(sp-lg)
|
|
181
|
+
line(length: 100%, stroke: border-thin + text-gray.lighten(40%))
|
|
182
|
+
v(sp-md)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#let line-items-table(
|
|
186
|
+
items,
|
|
187
|
+
tax-rate: none,
|
|
188
|
+
tax-label: "Tax",
|
|
189
|
+
currency: "$",
|
|
190
|
+
show-quantity: true,
|
|
191
|
+
) = {
|
|
192
|
+
// Calculate totals
|
|
193
|
+
let subtotal = items.fold(0, (acc, item) => acc + item.at("amount", default: 0))
|
|
194
|
+
let tax-amount = if tax-rate != none { subtotal * tax-rate } else { 0 }
|
|
195
|
+
let total = subtotal + tax-amount
|
|
196
|
+
|
|
197
|
+
// Format currency
|
|
198
|
+
let fmt(n) = {
|
|
199
|
+
currency + str(calc.round(n, digits: 2))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Header + items
|
|
203
|
+
table(
|
|
204
|
+
columns: if show-quantity {
|
|
205
|
+
(1fr, auto, auto, auto)
|
|
206
|
+
} else {
|
|
207
|
+
(1fr, auto)
|
|
208
|
+
},
|
|
209
|
+
inset: sp-sm,
|
|
210
|
+
stroke: (x, y) => if y == 0 {
|
|
211
|
+
(bottom: border-medium + accent)
|
|
212
|
+
} else {
|
|
213
|
+
(bottom: border-thin + text-gray.lighten(70%))
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
// Header row
|
|
217
|
+
..if show-quantity {
|
|
218
|
+
(
|
|
219
|
+
table.cell(text(weight: "bold", size: ts-small)[Description]),
|
|
220
|
+
table.cell(text(weight: "bold", size: ts-small)[Qty]),
|
|
221
|
+
table.cell(text(weight: "bold", size: ts-small)[Rate]),
|
|
222
|
+
table.cell(text(weight: "bold", size: ts-small, fill: text-dark)[Amount]),
|
|
223
|
+
)
|
|
224
|
+
} else {
|
|
225
|
+
(
|
|
226
|
+
table.cell(text(weight: "bold", size: ts-small)[Description]),
|
|
227
|
+
table.cell(text(weight: "bold", size: ts-small, fill: text-dark)[Amount]),
|
|
228
|
+
)
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
// Item rows
|
|
232
|
+
..items.map(item => {
|
|
233
|
+
if show-quantity {
|
|
234
|
+
(
|
|
235
|
+
text(size: ts-body)[#item.description],
|
|
236
|
+
align(right, text(size: ts-small)[#item.at("quantity", default: 1)]),
|
|
237
|
+
align(right, text(size: ts-small)[#fmt(item.at("rate", default: 0))]),
|
|
238
|
+
align(right, text(size: ts-body, weight: "medium")[#fmt(item.amount)]),
|
|
239
|
+
)
|
|
240
|
+
} else {
|
|
241
|
+
(
|
|
242
|
+
text(size: ts-body)[#item.description],
|
|
243
|
+
align(right, text(size: ts-body, weight: "medium")[#fmt(item.amount)]),
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
}).flatten(),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
// Totals section
|
|
250
|
+
v(sp-sm)
|
|
251
|
+
align(right)[
|
|
252
|
+
#grid(
|
|
253
|
+
columns: (auto, 6em),
|
|
254
|
+
row-gutter: sp-xs,
|
|
255
|
+
column-gutter: sp-lg,
|
|
256
|
+
text(size: ts-small, fill: text-gray)[Subtotal:],
|
|
257
|
+
align(right, text(size: ts-body)[#fmt(subtotal)]),
|
|
258
|
+
..if tax-rate != none {
|
|
259
|
+
(
|
|
260
|
+
text(size: ts-small, fill: text-gray)[#tax-label (#str(calc.round(tax-rate * 100, digits: 2))%):],
|
|
261
|
+
align(right, text(size: ts-body)[#fmt(tax-amount)]),
|
|
262
|
+
)
|
|
263
|
+
} else { () },
|
|
264
|
+
)
|
|
265
|
+
#v(sp-xs)
|
|
266
|
+
#line(length: 10em, stroke: border-thin + text-gray.lighten(40%))
|
|
267
|
+
#v(sp-xs)
|
|
268
|
+
#grid(
|
|
269
|
+
columns: (auto, 6em),
|
|
270
|
+
column-gutter: sp-lg,
|
|
271
|
+
text(size: 12pt, weight: "bold")[Total:],
|
|
272
|
+
align(right, text(size: 12pt, weight: "bold", fill: accent)[#fmt(total)]),
|
|
273
|
+
)
|
|
274
|
+
]
|
|
275
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Layout components: hero-section, two-column, sidebar-layout, card, card-grid
|
|
2
|
+
|
|
3
|
+
#import "tokens.typ": sp-xs, sp-sm, sp-md, sp-lg, sp-xl, sp-2xl, sp-3xl, ts-display, ts-body, border-thin, border-thick, radius-md
|
|
4
|
+
#import "colors.typ": accent, text-dark, text-gray, bg-card, border
|
|
5
|
+
#import "typography.typ": font-display
|
|
6
|
+
|
|
7
|
+
#let hero-section(
|
|
8
|
+
title: [],
|
|
9
|
+
subtitle: none,
|
|
10
|
+
accent-line: true,
|
|
11
|
+
fill: accent.lighten(92%),
|
|
12
|
+
text-fill: text-dark,
|
|
13
|
+
) = {
|
|
14
|
+
block(
|
|
15
|
+
width: 100%,
|
|
16
|
+
fill: fill,
|
|
17
|
+
inset: (x: sp-2xl, top: sp-3xl, bottom: sp-2xl),
|
|
18
|
+
below: sp-xl,
|
|
19
|
+
)[
|
|
20
|
+
#text(
|
|
21
|
+
font: font-display,
|
|
22
|
+
size: ts-display,
|
|
23
|
+
fill: text-fill,
|
|
24
|
+
weight: "bold",
|
|
25
|
+
)[#title]
|
|
26
|
+
#if subtitle != none {
|
|
27
|
+
v(sp-sm)
|
|
28
|
+
text(size: ts-body + 2pt, fill: text-gray)[#subtitle]
|
|
29
|
+
}
|
|
30
|
+
#if accent-line {
|
|
31
|
+
v(sp-md)
|
|
32
|
+
line(length: 80pt, stroke: border-thick + accent)
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#let two-column(
|
|
38
|
+
left,
|
|
39
|
+
right,
|
|
40
|
+
ratio: (2fr, 1fr),
|
|
41
|
+
gutter: sp-xl,
|
|
42
|
+
) = {
|
|
43
|
+
grid(
|
|
44
|
+
columns: ratio,
|
|
45
|
+
column-gutter: gutter,
|
|
46
|
+
left,
|
|
47
|
+
right,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#let sidebar-layout(
|
|
52
|
+
main,
|
|
53
|
+
sidebar,
|
|
54
|
+
sidebar-width: 2.2in,
|
|
55
|
+
gutter: sp-xl,
|
|
56
|
+
sidebar-position: "right",
|
|
57
|
+
) = {
|
|
58
|
+
let cols = if sidebar-position == "left" {
|
|
59
|
+
(sidebar-width, 1fr)
|
|
60
|
+
} else {
|
|
61
|
+
(1fr, sidebar-width)
|
|
62
|
+
}
|
|
63
|
+
let cells = if sidebar-position == "left" {
|
|
64
|
+
(sidebar, main)
|
|
65
|
+
} else {
|
|
66
|
+
(main, sidebar)
|
|
67
|
+
}
|
|
68
|
+
grid(
|
|
69
|
+
columns: cols,
|
|
70
|
+
column-gutter: gutter,
|
|
71
|
+
..cells,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#let card(body, title: none, fill: bg-card, stroke: border-thin + border) = {
|
|
76
|
+
block(
|
|
77
|
+
width: 100%,
|
|
78
|
+
fill: fill,
|
|
79
|
+
stroke: stroke,
|
|
80
|
+
radius: radius-md,
|
|
81
|
+
inset: sp-md,
|
|
82
|
+
above: sp-sm,
|
|
83
|
+
below: sp-sm,
|
|
84
|
+
)[
|
|
85
|
+
#if title != none {
|
|
86
|
+
text(weight: "bold", size: 11pt)[#title]
|
|
87
|
+
v(sp-xs)
|
|
88
|
+
}
|
|
89
|
+
#body
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#let card-grid(
|
|
94
|
+
cards,
|
|
95
|
+
columns: 3,
|
|
96
|
+
gutter: sp-md,
|
|
97
|
+
) = {
|
|
98
|
+
grid(
|
|
99
|
+
columns: (1fr,) * columns,
|
|
100
|
+
column-gutter: gutter,
|
|
101
|
+
row-gutter: gutter,
|
|
102
|
+
..cards,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Title block, info-table, signature-block, separator
|
|
2
|
+
|
|
3
|
+
#import "colors.typ": accent, text-gray
|
|
4
|
+
#import "typography.typ": font-display
|
|
5
|
+
#import "tokens.typ": ts-h1, ts-body, sp-xs, sp-sm, sp-md, border-thin
|
|
6
|
+
|
|
7
|
+
#let title-block(title: [], date: none) = {
|
|
8
|
+
align(left)[
|
|
9
|
+
#text(font: font-display, size: ts-h1, fill: accent, weight: "bold")[#title]
|
|
10
|
+
#v(sp-sm)
|
|
11
|
+
#if date != none [
|
|
12
|
+
#text(size: ts-body, fill: text-gray)[#date]
|
|
13
|
+
#v(sp-xs)
|
|
14
|
+
]
|
|
15
|
+
]
|
|
16
|
+
v(sp-sm)
|
|
17
|
+
pdf.artifact(line(length: 100%, stroke: border-thin + text-gray.lighten(40%)))
|
|
18
|
+
v(sp-md)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#let info-table(..pairs) = {
|
|
22
|
+
grid(
|
|
23
|
+
columns: (auto, 1fr),
|
|
24
|
+
row-gutter: sp-sm,
|
|
25
|
+
column-gutter: sp-md,
|
|
26
|
+
..pairs.pos().map(((label, value)) => {
|
|
27
|
+
(strong(label), value)
|
|
28
|
+
}).flatten()
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#let signature-block(party-name, include-title: true) = {
|
|
33
|
+
v(1em)
|
|
34
|
+
strong(party-name)
|
|
35
|
+
v(2em)
|
|
36
|
+
grid(
|
|
37
|
+
columns: (1fr, auto),
|
|
38
|
+
column-gutter: 2em,
|
|
39
|
+
[Name: #box(width: 12em, stroke: (bottom: border-thin))],
|
|
40
|
+
[Date: #box(width: 6em, stroke: (bottom: border-thin))],
|
|
41
|
+
)
|
|
42
|
+
if include-title {
|
|
43
|
+
v(1em)
|
|
44
|
+
[Title: #box(width: 12em, stroke: (bottom: border-thin))]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#let separator() = {
|
|
49
|
+
pdf.artifact(line(length: 100%, stroke: border-thin + text-gray.lighten(40%)))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Re-export component modules (barrel file)
|
|
53
|
+
#import "components-layout.typ": *
|
|
54
|
+
#import "components-content.typ": *
|
|
55
|
+
#import "components-document.typ": *
|