@neptune.fintech/icons 2.1.0 → 2.3.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/NOTICE-brand-marks.md +12 -5
- package/README.md +62 -12
- package/dist/brand-marks.d.ts +45 -11
- package/dist/brand-marks.d.ts.map +1 -1
- package/dist/brand-marks.js +813 -146
- package/dist/brand-marks.js.map +1 -1
- package/dist/element.d.ts +15 -5
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +27 -8
- package/dist/element.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/brand-marks.js
CHANGED
|
@@ -8,147 +8,578 @@
|
|
|
8
8
|
// TRADEMARKS owned by their respective owners. They are provided here ONLY as
|
|
9
9
|
// SIMPLIFIED, SCHEMATIC IDENTIFICATION MARKS / PLACEHOLDERS so a UI can label a
|
|
10
10
|
// payment method — they are deliberately NOT pixel-exact reproductions of any
|
|
11
|
-
// official logo.
|
|
11
|
+
// official logo, and they are NOT traced from any official artwork. They are
|
|
12
|
+
// ORIGINAL, clean geometric placeholders authored for Neptune Odyssey.
|
|
12
13
|
//
|
|
13
|
-
// These marks are NOT
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
14
|
+
// These marks are NOT licensed under the Neptune Odyssey Community License and
|
|
15
|
+
// are kept SEPARATE from the monochrome ICONS set on purpose. In production,
|
|
16
|
+
// replace each with the brand's OFFICIAL asset via registerBrandMark() (see
|
|
17
|
+
// below) and follow that brand's brand/usage guidelines.
|
|
17
18
|
//
|
|
18
19
|
// The Libyan / local marks (NUMO, Moamalat, LyPay, OnePay, Sadad, Tadawul) are
|
|
19
|
-
// NEUTRAL PLACEHOLDERS (a simple badge + initials) — we do not ship their
|
|
20
|
-
// assets. Replace them with official artwork before shipping.
|
|
20
|
+
// NEUTRAL PLACEHOLDERS (a simple badge + initials/motif) — we do not ship their
|
|
21
|
+
// real assets. Replace them with official artwork before shipping.
|
|
21
22
|
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
23
|
+
// ── THREE-VARIANT SYSTEM ────────────────────────────────────────────────────
|
|
24
|
+
// Every mark is authored as a small array of shape PRIMITIVES tagged by ROLE,
|
|
25
|
+
// plus a per-mark brand-colour map. It then renders in three variants:
|
|
26
|
+
// • "color" — multicolour, brand colours by role. The default.
|
|
27
|
+
// • "mono" — a single flat silhouette in `currentColor` (fills only).
|
|
28
|
+
// • "outline" — line style: stroke="currentColor", fill="none", round joins.
|
|
29
|
+
// Licensed users can drop in a brand's official SVG with registerBrandMark();
|
|
30
|
+
// after that, brandMarkSvg() returns the override.
|
|
31
|
+
// Neutral framing colours shared by the "card body" marks (not brand colours).
|
|
32
|
+
const CARD_BG = "#F4F5F7";
|
|
33
|
+
const CARD_HAIRLINE = "#E1E3E8";
|
|
34
|
+
// A neutral card body + hairline frame, as two shapes. Reused by many marks.
|
|
35
|
+
function cardFrame(bg = CARD_BG) {
|
|
36
|
+
return [
|
|
37
|
+
{ kind: "rect", role: "bg", attrs: { width: 48, height: 32, rx: 4, fill: bg } },
|
|
38
|
+
{
|
|
39
|
+
kind: "rect",
|
|
40
|
+
role: "hairline",
|
|
41
|
+
attrs: { x: 0.5, y: 0.5, width: 47, height: 31, rx: 3.5 },
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
const FONT = "Arial, Helvetica, sans-serif";
|
|
46
|
+
// Arabic-capable stack — the local Libyan marks carry their native wordmark.
|
|
47
|
+
const AR_FONT = "'Geeza Pro', 'Noto Sans Arabic', Tahoma, 'Segoe UI', Arial, sans-serif";
|
|
24
48
|
/**
|
|
25
|
-
* name →
|
|
26
|
-
*
|
|
27
|
-
* Card-ratio canvas (viewBox 0 0 48 32) for wide marks; 32×32 for square
|
|
28
|
-
* badges. A neutral card body (#F4F5F7 fill, #E1E3E8 hairline) frames most
|
|
29
|
-
* marks so they sit well on any surface.
|
|
49
|
+
* name → mark definition. Each is an ORIGINAL simplified geometric placeholder
|
|
50
|
+
* recognisable by the brand's colours/basic forms — never traced artwork.
|
|
30
51
|
*/
|
|
31
|
-
|
|
52
|
+
const MARK_DEFS = {
|
|
32
53
|
// ── Card networks ───────────────────────────────────────────────────
|
|
33
|
-
visa:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
visa: {
|
|
55
|
+
viewBox: "0 0 48 32",
|
|
56
|
+
label: "Visa",
|
|
57
|
+
colors: { a: "#1A1F71", ink: "#1A1F71" },
|
|
58
|
+
shapes: [
|
|
59
|
+
...cardFrame(),
|
|
60
|
+
{
|
|
61
|
+
kind: "text",
|
|
62
|
+
role: "a",
|
|
63
|
+
text: "VISA",
|
|
64
|
+
attrs: {
|
|
65
|
+
x: 24,
|
|
66
|
+
y: 21.5,
|
|
67
|
+
"font-family": FONT,
|
|
68
|
+
"font-size": 13,
|
|
69
|
+
"font-style": "italic",
|
|
70
|
+
"font-weight": 700,
|
|
71
|
+
"text-anchor": "middle",
|
|
72
|
+
"letter-spacing": 0.5,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
mastercard: {
|
|
78
|
+
viewBox: "0 0 48 32",
|
|
79
|
+
label: "Mastercard",
|
|
80
|
+
colors: { a: "#EB001B", b: "#F79E1B", c: "#FF5F00" },
|
|
81
|
+
shapes: [
|
|
82
|
+
...cardFrame(),
|
|
83
|
+
{ kind: "circle", role: "a", attrs: { cx: 20, cy: 16, r: 8 } },
|
|
84
|
+
{ kind: "circle", role: "b", attrs: { cx: 28, cy: 16, r: 8 } },
|
|
85
|
+
{
|
|
86
|
+
kind: "path",
|
|
87
|
+
role: "c",
|
|
88
|
+
attrs: { d: "M24 9.7a8 8 0 0 0 0 12.6 8 8 0 0 0 0-12.6Z" },
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
amex: {
|
|
93
|
+
viewBox: "0 0 48 32",
|
|
94
|
+
label: "American Express",
|
|
95
|
+
colors: { a: "#2E77BC", ink: "#FFFFFF" },
|
|
96
|
+
shapes: [
|
|
97
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
98
|
+
{
|
|
99
|
+
kind: "rect",
|
|
100
|
+
role: "ink",
|
|
101
|
+
attrs: { x: 6, y: 9, width: 36, height: 14, rx: 2, "fill-opacity": 0.12 },
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
kind: "text",
|
|
105
|
+
role: "ink",
|
|
106
|
+
text: "AMEX",
|
|
107
|
+
attrs: {
|
|
108
|
+
x: 24,
|
|
109
|
+
y: 20.5,
|
|
110
|
+
"font-family": FONT,
|
|
111
|
+
"font-size": 11,
|
|
112
|
+
"font-weight": 700,
|
|
113
|
+
"text-anchor": "middle",
|
|
114
|
+
"letter-spacing": 1,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
discover: {
|
|
120
|
+
viewBox: "0 0 48 32",
|
|
121
|
+
label: "Discover",
|
|
122
|
+
colors: { ink: "#231F20", a: "#F58220" },
|
|
123
|
+
shapes: [
|
|
124
|
+
...cardFrame(),
|
|
125
|
+
{
|
|
126
|
+
kind: "text",
|
|
127
|
+
role: "ink",
|
|
128
|
+
text: "DISC",
|
|
129
|
+
attrs: {
|
|
130
|
+
x: 21,
|
|
131
|
+
y: 20.5,
|
|
132
|
+
"font-family": FONT,
|
|
133
|
+
"font-size": 9,
|
|
134
|
+
"font-weight": 700,
|
|
135
|
+
"text-anchor": "middle",
|
|
136
|
+
"letter-spacing": 0.3,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{ kind: "circle", role: "a", attrs: { cx: 36, cy: 17, r: 6 } },
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
unionpay: {
|
|
143
|
+
viewBox: "0 0 48 32",
|
|
144
|
+
label: "UnionPay",
|
|
145
|
+
colors: { a: "#E21836", b: "#00447C", c: "#007B84", ink: "#FFFFFF" },
|
|
146
|
+
shapes: [
|
|
147
|
+
...cardFrame(),
|
|
148
|
+
{ kind: "rect", role: "a", attrs: { x: 10, y: 7, width: 9, height: 18, rx: 2 } },
|
|
149
|
+
{ kind: "rect", role: "b", attrs: { x: 19, y: 7, width: 9, height: 18, rx: 2 } },
|
|
150
|
+
{ kind: "rect", role: "c", attrs: { x: 28, y: 7, width: 9, height: 18, rx: 2 } },
|
|
151
|
+
{
|
|
152
|
+
kind: "text",
|
|
153
|
+
role: "ink",
|
|
154
|
+
text: "UPAY",
|
|
155
|
+
attrs: {
|
|
156
|
+
x: 24,
|
|
157
|
+
y: 19.5,
|
|
158
|
+
"font-family": FONT,
|
|
159
|
+
"font-size": 6,
|
|
160
|
+
"font-weight": 700,
|
|
161
|
+
"text-anchor": "middle",
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
59
166
|
// ── Money transfer ──────────────────────────────────────────────────
|
|
60
|
-
"western-union":
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
167
|
+
"western-union": {
|
|
168
|
+
viewBox: "0 0 48 32",
|
|
169
|
+
label: "Western Union",
|
|
170
|
+
colors: { a: "#FFDD00", ink: "#000000" },
|
|
171
|
+
shapes: [
|
|
172
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
173
|
+
{
|
|
174
|
+
kind: "text",
|
|
175
|
+
role: "ink",
|
|
176
|
+
text: "WESTERN",
|
|
177
|
+
attrs: {
|
|
178
|
+
x: 24,
|
|
179
|
+
y: 14.5,
|
|
180
|
+
"font-family": FONT,
|
|
181
|
+
"font-size": 7,
|
|
182
|
+
"font-weight": 700,
|
|
183
|
+
"text-anchor": "middle",
|
|
184
|
+
"letter-spacing": 0.5,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
kind: "text",
|
|
189
|
+
role: "ink",
|
|
190
|
+
text: "UNION",
|
|
191
|
+
attrs: {
|
|
192
|
+
x: 24,
|
|
193
|
+
y: 23.5,
|
|
194
|
+
"font-family": FONT,
|
|
195
|
+
"font-size": 7,
|
|
196
|
+
"font-weight": 700,
|
|
197
|
+
"text-anchor": "middle",
|
|
198
|
+
"letter-spacing": 0.5,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
moneygram: {
|
|
204
|
+
viewBox: "0 0 48 32",
|
|
205
|
+
label: "MoneyGram",
|
|
206
|
+
colors: { a: "#E51937", ink: "#E51937" },
|
|
207
|
+
shapes: [
|
|
208
|
+
...cardFrame(),
|
|
209
|
+
{ kind: "circle", role: "a", attrs: { cx: 13, cy: 16, r: 5 } },
|
|
210
|
+
{
|
|
211
|
+
kind: "text",
|
|
212
|
+
role: "ink",
|
|
213
|
+
text: "MGRAM",
|
|
214
|
+
attrs: {
|
|
215
|
+
x: 29,
|
|
216
|
+
y: 19.5,
|
|
217
|
+
"font-family": FONT,
|
|
218
|
+
"font-size": 7,
|
|
219
|
+
"font-weight": 700,
|
|
220
|
+
"text-anchor": "middle",
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
70
225
|
// ── Libyan / local — PLACEHOLDERS (replace with official assets) ─────
|
|
71
|
-
numo:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
226
|
+
numo: {
|
|
227
|
+
viewBox: "0 0 48 32",
|
|
228
|
+
label: "NUMO — Moamalat national card scheme (placeholder mark)",
|
|
229
|
+
placeholder: true,
|
|
230
|
+
colors: { a: "#0C2A4A", b: "#D7A52B", ink: "#FFFFFF" },
|
|
231
|
+
shapes: [
|
|
232
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
233
|
+
// gold accent dot + underline frame the wordmark
|
|
234
|
+
{ kind: "circle", role: "b", attrs: { cx: 35, cy: 11, r: 2.3 } },
|
|
235
|
+
{
|
|
236
|
+
kind: "text",
|
|
237
|
+
role: "ink",
|
|
238
|
+
text: "NUMO",
|
|
239
|
+
attrs: {
|
|
240
|
+
x: 24,
|
|
241
|
+
y: 19,
|
|
242
|
+
"font-family": FONT,
|
|
243
|
+
"font-size": 11,
|
|
244
|
+
"font-weight": 800,
|
|
245
|
+
"text-anchor": "middle",
|
|
246
|
+
"letter-spacing": 1.2,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
kind: "path",
|
|
251
|
+
role: "b",
|
|
252
|
+
attrs: { d: "M15 24 H33", fill: "none", "stroke-width": 1.6, "stroke-linecap": "round" },
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
moamalat: {
|
|
257
|
+
viewBox: "0 0 48 32",
|
|
258
|
+
label: "Moamalat (placeholder mark)",
|
|
259
|
+
placeholder: true,
|
|
260
|
+
colors: { a: "#C8992C", ink: "#243240" },
|
|
261
|
+
shapes: [
|
|
262
|
+
...cardFrame(),
|
|
263
|
+
// gold flowing double-arch "M" monogram (original geometry)
|
|
264
|
+
{
|
|
265
|
+
kind: "path",
|
|
266
|
+
role: "a",
|
|
267
|
+
attrs: {
|
|
268
|
+
d: "M18 22 V14.5 q0 -3.6 3 -3.6 q3 0 3 3.6 V22 M24 22 V14.5 q0 -3.6 3 -3.6 q3 0 3 3.6 V22",
|
|
269
|
+
fill: "none",
|
|
270
|
+
"stroke-width": 2.4,
|
|
271
|
+
"stroke-linecap": "round",
|
|
272
|
+
"stroke-linejoin": "round",
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
kind: "text",
|
|
277
|
+
role: "ink",
|
|
278
|
+
text: "moamalat",
|
|
279
|
+
attrs: {
|
|
280
|
+
x: 24,
|
|
281
|
+
y: 28.5,
|
|
282
|
+
"font-family": FONT,
|
|
283
|
+
"font-size": 6.5,
|
|
284
|
+
"font-weight": 700,
|
|
285
|
+
"text-anchor": "middle",
|
|
286
|
+
"letter-spacing": 0.2,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
// LyPay — the green→teal→blue swoosh "flag" + LYPay wordmark + Arabic لي باي.
|
|
292
|
+
lypay: {
|
|
293
|
+
viewBox: "0 0 48 32",
|
|
294
|
+
label: "LyPay (placeholder mark)",
|
|
295
|
+
placeholder: true,
|
|
296
|
+
colors: { a: "#27B36A", b: "#1AA0B4", c: "#2E78D6", ink: "#0F3A33" },
|
|
297
|
+
shapes: [
|
|
298
|
+
...cardFrame("#EFFAF5"),
|
|
299
|
+
{ kind: "path", role: "a", attrs: { d: "M6 22 C10 22 15 19 20 13", fill: "none", "stroke-width": 2.2, "stroke-linecap": "round" } },
|
|
300
|
+
{ kind: "path", role: "b", attrs: { d: "M6 19 C10 19 15 16 20 10", fill: "none", "stroke-width": 2.2, "stroke-linecap": "round" } },
|
|
301
|
+
{ kind: "path", role: "c", attrs: { d: "M6 16 C10 16 15 13 20 7", fill: "none", "stroke-width": 2.2, "stroke-linecap": "round" } },
|
|
302
|
+
{
|
|
303
|
+
kind: "text",
|
|
304
|
+
role: "ink",
|
|
305
|
+
text: "LYPay",
|
|
306
|
+
attrs: { x: 35, y: 17.5, "font-family": FONT, "font-size": 8.5, "font-weight": 800, "text-anchor": "middle", "letter-spacing": 0.2 },
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
kind: "text",
|
|
310
|
+
role: "a",
|
|
311
|
+
text: "لي باي",
|
|
312
|
+
attrs: { x: 35, y: 26, "font-family": AR_FONT, "font-size": 5.5, "font-weight": 700, "text-anchor": "middle", direction: "rtl" },
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
// OnePay — the blue "One Pay" wordmark + Arabic وان باي.
|
|
317
|
+
onepay: {
|
|
318
|
+
viewBox: "0 0 48 32",
|
|
319
|
+
label: "OnePay (placeholder mark)",
|
|
320
|
+
placeholder: true,
|
|
321
|
+
colors: { a: "#1565C0", ink: "#1B2733" },
|
|
322
|
+
shapes: [
|
|
323
|
+
...cardFrame(),
|
|
324
|
+
{ kind: "text", role: "a", text: "One", attrs: { x: 23.5, y: 18.5, "font-family": FONT, "font-size": 10, "font-weight": 800, "text-anchor": "end" } },
|
|
325
|
+
{ kind: "text", role: "ink", text: "Pay", attrs: { x: 24.5, y: 18.5, "font-family": FONT, "font-size": 10, "font-weight": 800, "text-anchor": "start" } },
|
|
326
|
+
{ kind: "text", role: "a", text: "وان باي", attrs: { x: 24, y: 27, "font-family": AR_FONT, "font-size": 5.5, "font-weight": 700, "text-anchor": "middle", direction: "rtl" } },
|
|
327
|
+
],
|
|
328
|
+
},
|
|
329
|
+
// Sadad — the Arabic سداد script as the hero + Latin SADAD.
|
|
330
|
+
sadad: {
|
|
331
|
+
viewBox: "0 0 48 32",
|
|
332
|
+
label: "Sadad (placeholder mark)",
|
|
333
|
+
placeholder: true,
|
|
334
|
+
colors: { a: "#E2601A", ink: "#243240" },
|
|
335
|
+
shapes: [
|
|
336
|
+
...cardFrame(),
|
|
337
|
+
{ kind: "text", role: "a", text: "سداد", attrs: { x: 24, y: 19, "font-family": AR_FONT, "font-size": 13, "font-weight": 700, "text-anchor": "middle", direction: "rtl" } },
|
|
338
|
+
{ kind: "text", role: "ink", text: "SADAD", attrs: { x: 24, y: 27, "font-family": FONT, "font-size": 5.5, "font-weight": 700, "text-anchor": "middle", "letter-spacing": 1.6 } },
|
|
339
|
+
],
|
|
340
|
+
},
|
|
341
|
+
// Tadawul — a teal up-trend mark + Tadawul wordmark + Arabic تداول.
|
|
342
|
+
tadawul: {
|
|
343
|
+
viewBox: "0 0 48 32",
|
|
344
|
+
label: "Tadawul (placeholder mark)",
|
|
345
|
+
placeholder: true,
|
|
346
|
+
colors: { a: "#0E7C73", b: "#39C2B0", ink: "#243240" },
|
|
347
|
+
shapes: [
|
|
348
|
+
...cardFrame(),
|
|
349
|
+
{ kind: "path", role: "a", attrs: { d: "M7 21 L12 16 L16 18.5 L21 11", fill: "none", "stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round" } },
|
|
350
|
+
{ kind: "circle", role: "b", attrs: { cx: 21, cy: 11, r: 1.9 } },
|
|
351
|
+
{ kind: "text", role: "ink", text: "Tadawul", attrs: { x: 34, y: 17.5, "font-family": FONT, "font-size": 6.5, "font-weight": 700, "text-anchor": "middle" } },
|
|
352
|
+
{ kind: "text", role: "a", text: "تداول", attrs: { x: 34, y: 26, "font-family": AR_FONT, "font-size": 5.5, "font-weight": 700, "text-anchor": "middle", direction: "rtl" } },
|
|
353
|
+
],
|
|
354
|
+
},
|
|
103
355
|
// ── Wallets / generic ───────────────────────────────────────────────
|
|
104
|
-
"apple-pay":
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
356
|
+
"apple-pay": {
|
|
357
|
+
viewBox: "0 0 48 32",
|
|
358
|
+
label: "Apple Pay",
|
|
359
|
+
colors: { ink: "#000000" },
|
|
360
|
+
shapes: [
|
|
361
|
+
...cardFrame("#FFFFFF"),
|
|
362
|
+
{
|
|
363
|
+
kind: "path",
|
|
364
|
+
role: "ink",
|
|
365
|
+
attrs: {
|
|
366
|
+
d: "M14.4 12.1c.5-.6.8-1.4.7-2.2-.7 0-1.5.5-2 1.1-.4.5-.8 1.3-.7 2.1.8.1 1.5-.4 2-1Zm.7 1.1c-1.1-.1-2 .6-2.5.6-.5 0-1.3-.6-2.1-.6-1.1 0-2.1.6-2.7 1.6-1.1 2-.3 4.9.8 6.5.5.8 1.2 1.7 2 1.6.8 0 1.1-.5 2.1-.5s1.3.5 2.1.5c.9 0 1.4-.8 2-1.5.6-.9.8-1.7.9-1.8 0 0-1.7-.7-1.7-2.6 0-1.6 1.3-2.4 1.4-2.4-.8-1.1-2-1.4-2.3-1.5Z",
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
kind: "text",
|
|
371
|
+
role: "ink",
|
|
372
|
+
text: "Pay",
|
|
373
|
+
attrs: {
|
|
374
|
+
x: 33,
|
|
375
|
+
y: 19.5,
|
|
376
|
+
"font-family": FONT,
|
|
377
|
+
"font-size": 9,
|
|
378
|
+
"font-weight": 600,
|
|
379
|
+
"text-anchor": "middle",
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
},
|
|
384
|
+
"google-pay": {
|
|
385
|
+
viewBox: "0 0 48 32",
|
|
386
|
+
label: "Google Pay",
|
|
387
|
+
colors: { a: "#4285F4", b: "#34A853", ink: "#5F6368" },
|
|
388
|
+
shapes: [
|
|
389
|
+
...cardFrame("#FFFFFF"),
|
|
390
|
+
{
|
|
391
|
+
kind: "path",
|
|
392
|
+
role: "a",
|
|
393
|
+
attrs: {
|
|
394
|
+
d: "M16 16.2v2.1h3a2.6 2.6 0 0 1-1.1 1.7 3.2 3.2 0 1 1-1-4.6l1.5-1.5a5.3 5.3 0 1 0 1.6 4.5h-5Z",
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
kind: "path",
|
|
399
|
+
role: "b",
|
|
400
|
+
attrs: { d: "M19 16.2h-3v2.1h3a3 3 0 0 0 .1-.8 4 4 0 0 0-.1-1.3Z" },
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
kind: "text",
|
|
404
|
+
role: "ink",
|
|
405
|
+
text: "Pay",
|
|
406
|
+
attrs: {
|
|
407
|
+
x: 33,
|
|
408
|
+
y: 19.5,
|
|
409
|
+
"font-family": FONT,
|
|
410
|
+
"font-size": 9,
|
|
411
|
+
"font-weight": 600,
|
|
412
|
+
"text-anchor": "middle",
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
paypal: {
|
|
418
|
+
viewBox: "0 0 48 32",
|
|
419
|
+
label: "PayPal",
|
|
420
|
+
colors: { a: "#003087", b: "#009CDE", ink: "#003087" },
|
|
421
|
+
shapes: [
|
|
422
|
+
...cardFrame(),
|
|
423
|
+
{
|
|
424
|
+
kind: "path",
|
|
425
|
+
role: "a",
|
|
426
|
+
attrs: { d: "M15 9h5.2c2.4 0 4 1.4 3.6 3.9-.4 2.6-2.4 3.9-4.9 3.9h-1.7l-.7 4.3h-2.9L15 9Z" },
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
kind: "path",
|
|
430
|
+
role: "b",
|
|
431
|
+
attrs: { d: "M18 11h4.3c2.4 0 4 1.4 3.6 3.9-.4 2.6-2.4 3.9-4.9 3.9h-1.7l-.7 4.3h-2.9L18 11Z" },
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
kind: "text",
|
|
435
|
+
role: "ink",
|
|
436
|
+
text: "Pal",
|
|
437
|
+
attrs: {
|
|
438
|
+
x: 34,
|
|
439
|
+
y: 20,
|
|
440
|
+
"font-family": FONT,
|
|
441
|
+
"font-size": 7,
|
|
442
|
+
"font-weight": 700,
|
|
443
|
+
"text-anchor": "middle",
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
},
|
|
448
|
+
swift: {
|
|
449
|
+
viewBox: "0 0 48 32",
|
|
450
|
+
label: "SWIFT",
|
|
451
|
+
colors: { a: "#0033A0", ink: "#FFFFFF" },
|
|
452
|
+
shapes: [
|
|
453
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
454
|
+
{
|
|
455
|
+
kind: "text",
|
|
456
|
+
role: "ink",
|
|
457
|
+
text: "SWIFT",
|
|
458
|
+
attrs: {
|
|
459
|
+
x: 24,
|
|
460
|
+
y: 20.5,
|
|
461
|
+
"font-family": FONT,
|
|
462
|
+
"font-size": 10,
|
|
463
|
+
"font-weight": 700,
|
|
464
|
+
"text-anchor": "middle",
|
|
465
|
+
"letter-spacing": 1.5,
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
},
|
|
470
|
+
mada: {
|
|
471
|
+
viewBox: "0 0 48 32",
|
|
472
|
+
label: "mada-style domestic scheme (generic)",
|
|
473
|
+
placeholder: true,
|
|
474
|
+
colors: { a: "#84BD00", b: "#1F3661" },
|
|
475
|
+
shapes: [
|
|
476
|
+
...cardFrame(),
|
|
477
|
+
{ kind: "rect", role: "a", attrs: { x: 9, y: 13, width: 14, height: 6, rx: 3 } },
|
|
478
|
+
{ kind: "rect", role: "b", attrs: { x: 25, y: 13, width: 14, height: 6, rx: 3 } },
|
|
479
|
+
],
|
|
480
|
+
},
|
|
481
|
+
"generic-card": {
|
|
482
|
+
viewBox: "0 0 48 32",
|
|
483
|
+
label: "Card",
|
|
484
|
+
colors: { a: "#3C4858", b: "#E8C56B", ink: "#FFFFFF" },
|
|
485
|
+
shapes: [
|
|
486
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
487
|
+
{ kind: "rect", role: "b", attrs: { x: 6, y: 11, width: 8, height: 6, rx: 1.2 } },
|
|
488
|
+
{
|
|
489
|
+
kind: "path",
|
|
490
|
+
role: "ink",
|
|
491
|
+
attrs: {
|
|
492
|
+
d: "M6 22h20",
|
|
493
|
+
fill: "none",
|
|
494
|
+
"stroke-width": 1.4,
|
|
495
|
+
"stroke-linecap": "round",
|
|
496
|
+
"stroke-opacity": 0.7,
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
kind: "path",
|
|
501
|
+
role: "ink",
|
|
502
|
+
attrs: {
|
|
503
|
+
d: "M30 22h12",
|
|
504
|
+
fill: "none",
|
|
505
|
+
"stroke-width": 1.4,
|
|
506
|
+
"stroke-linecap": "round",
|
|
507
|
+
"stroke-opacity": 0.4,
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
"contactless-pay": {
|
|
513
|
+
viewBox: "0 0 48 32",
|
|
514
|
+
label: "Contactless payment",
|
|
515
|
+
colors: { a: "#1F6FEB" },
|
|
516
|
+
shapes: [
|
|
517
|
+
...cardFrame(),
|
|
518
|
+
{
|
|
519
|
+
kind: "g",
|
|
520
|
+
role: "a",
|
|
521
|
+
attrs: { fill: "none", "stroke-width": 1.8, "stroke-linecap": "round" },
|
|
522
|
+
children: [
|
|
523
|
+
{ kind: "path", role: "a", attrs: { d: "M18 12a8 8 0 0 1 0 8" } },
|
|
524
|
+
{ kind: "path", role: "a", attrs: { d: "M22 9.5a12 12 0 0 1 0 13" } },
|
|
525
|
+
{ kind: "path", role: "a", attrs: { d: "M26 7.5a16 16 0 0 1 0 17" } },
|
|
526
|
+
],
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
},
|
|
530
|
+
cash: {
|
|
531
|
+
viewBox: "0 0 48 32",
|
|
532
|
+
label: "Cash",
|
|
533
|
+
colors: { a: "#2E7D32", b: "#A5D6A7", ink: "#A5D6A7" },
|
|
534
|
+
shapes: [
|
|
535
|
+
...cardFrame("#E8F5E9"),
|
|
536
|
+
{ kind: "rect", role: "a", attrs: { x: 9, y: 9, width: 30, height: 14, rx: 2 } },
|
|
537
|
+
{
|
|
538
|
+
kind: "circle",
|
|
539
|
+
role: "b",
|
|
540
|
+
attrs: { cx: 24, cy: 16, r: 4, fill: "none", "stroke-width": 1.4 },
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
kind: "text",
|
|
544
|
+
role: "ink",
|
|
545
|
+
text: "$",
|
|
546
|
+
attrs: {
|
|
547
|
+
x: 24,
|
|
548
|
+
y: 18.5,
|
|
549
|
+
"font-family": FONT,
|
|
550
|
+
"font-size": 6,
|
|
551
|
+
"font-weight": 700,
|
|
552
|
+
"text-anchor": "middle",
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
},
|
|
557
|
+
"bank-building": {
|
|
558
|
+
viewBox: "0 0 48 32",
|
|
559
|
+
label: "Bank",
|
|
560
|
+
colors: { a: "#2A3A5A" },
|
|
561
|
+
shapes: [
|
|
562
|
+
...cardFrame("#EEF1F6"),
|
|
563
|
+
{
|
|
564
|
+
kind: "g",
|
|
565
|
+
role: "a",
|
|
566
|
+
attrs: {
|
|
567
|
+
fill: "none",
|
|
568
|
+
"stroke-width": 1.6,
|
|
569
|
+
"stroke-linecap": "round",
|
|
570
|
+
"stroke-linejoin": "round",
|
|
571
|
+
},
|
|
572
|
+
children: [
|
|
573
|
+
{ kind: "path", role: "a", attrs: { d: "M14 14l10-5 10 5" } },
|
|
574
|
+
{ kind: "path", role: "a", attrs: { d: "M16 14v7" } },
|
|
575
|
+
{ kind: "path", role: "a", attrs: { d: "M21 14v7" } },
|
|
576
|
+
{ kind: "path", role: "a", attrs: { d: "M27 14v7" } },
|
|
577
|
+
{ kind: "path", role: "a", attrs: { d: "M32 14v7" } },
|
|
578
|
+
{ kind: "path", role: "a", attrs: { d: "M13 23h22" } },
|
|
579
|
+
],
|
|
580
|
+
},
|
|
581
|
+
],
|
|
582
|
+
},
|
|
152
583
|
};
|
|
153
584
|
/** All brand-mark names, in catalogue order. */
|
|
154
585
|
export const BRAND_MARK_NAMES = [
|
|
@@ -177,29 +608,265 @@ export const BRAND_MARK_NAMES = [
|
|
|
177
608
|
];
|
|
178
609
|
/** True when `name` is a known brand mark. Acts as a type guard. */
|
|
179
610
|
export function isBrandMarkName(name) {
|
|
180
|
-
return Object.prototype.hasOwnProperty.call(
|
|
611
|
+
return Object.prototype.hasOwnProperty.call(MARK_DEFS, name);
|
|
612
|
+
}
|
|
613
|
+
// ── Rendering ───────────────────────────────────────────────────────────────
|
|
614
|
+
const escapeAttr = (v) => v.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
615
|
+
const escapeText = (v) => v.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
616
|
+
/** The single SVG tag for a shape kind ('g' has children, 'text' has a body). */
|
|
617
|
+
const SELF_CLOSING = {
|
|
618
|
+
rect: true,
|
|
619
|
+
circle: true,
|
|
620
|
+
ellipse: true,
|
|
621
|
+
path: true,
|
|
622
|
+
line: true,
|
|
623
|
+
text: false,
|
|
624
|
+
g: false,
|
|
625
|
+
};
|
|
626
|
+
/** A rect that fills the whole canvas — a coloured card body / backdrop. */
|
|
627
|
+
function isFullBleedBackdrop(shape, def) {
|
|
628
|
+
if (shape.kind !== "rect")
|
|
629
|
+
return false;
|
|
630
|
+
const [, , vbW, vbH] = def.viewBox.split(" ").map(Number);
|
|
631
|
+
const w = Number(shape.attrs.width);
|
|
632
|
+
const h = Number(shape.attrs.height);
|
|
633
|
+
// Anchored at the origin (no x/y, or 0) and spanning the full viewBox.
|
|
634
|
+
const x = Number(shape.attrs.x ?? 0);
|
|
635
|
+
const y = Number(shape.attrs.y ?? 0);
|
|
636
|
+
return x === 0 && y === 0 && w === vbW && h === vbH;
|
|
181
637
|
}
|
|
182
638
|
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
639
|
+
* Resolve the paint attributes a shape gets in a given variant.
|
|
640
|
+
* - color → brand colour by role; strokes (fill:none in attrs) keep stroke=colour.
|
|
641
|
+
* - mono → everything is a flat `currentColor` silhouette (fill); neutral
|
|
642
|
+
* frames AND full-bleed coloured backdrops are dropped so the inked
|
|
643
|
+
* glyph reads as the silhouette (e.g. AMEX/SWIFT lettering).
|
|
644
|
+
* - outline → stroke="currentColor", fill="none", round joins; frames/backdrops
|
|
645
|
+
* dropped.
|
|
646
|
+
* Returns null when the shape should be omitted in this variant.
|
|
186
647
|
*/
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
648
|
+
function paintFor(shape, variant, def) {
|
|
649
|
+
const isStrokeShape = shape.attrs.fill === "none" || shape.attrs["stroke-width"] !== undefined;
|
|
650
|
+
const isFrame = shape.role === "bg" || shape.role === "hairline";
|
|
651
|
+
if (variant === "color") {
|
|
652
|
+
const out = {};
|
|
653
|
+
if (isFrame) {
|
|
654
|
+
// Neutral frame keeps its literal colours (bg fill / hairline stroke).
|
|
655
|
+
if (shape.role === "bg") {
|
|
656
|
+
out.fill = shape.attrs.fill ?? CARD_BG;
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
out.fill = "none";
|
|
660
|
+
out.stroke = CARD_HAIRLINE;
|
|
661
|
+
}
|
|
662
|
+
return out;
|
|
663
|
+
}
|
|
664
|
+
const brand = def.colors[shape.role] ?? shape.attrs.fill ?? "#000000";
|
|
665
|
+
if (isStrokeShape) {
|
|
666
|
+
out.fill = "none";
|
|
667
|
+
out.stroke = brand;
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
out.fill = brand;
|
|
671
|
+
}
|
|
672
|
+
return out;
|
|
673
|
+
}
|
|
674
|
+
// mono + outline: drop the neutral frame and any full-bleed coloured backdrop,
|
|
675
|
+
// so the foreground glyph (text/paths) becomes the silhouette.
|
|
676
|
+
if (isFrame || isFullBleedBackdrop(shape, def))
|
|
677
|
+
return null;
|
|
678
|
+
if (variant === "mono") {
|
|
679
|
+
// Flatten to a single currentColor silhouette. Stroke shapes stay strokes
|
|
680
|
+
// (so line-art marks like contactless/bank still read), filled shapes fill.
|
|
681
|
+
if (isStrokeShape) {
|
|
682
|
+
return { fill: "none", stroke: "currentColor" };
|
|
683
|
+
}
|
|
684
|
+
return { fill: "currentColor" };
|
|
685
|
+
}
|
|
686
|
+
// outline
|
|
687
|
+
return { fill: "none", stroke: "currentColor" };
|
|
688
|
+
}
|
|
689
|
+
/** Stroke-width to use for a shape in mono/outline (keeps line marks tidy). */
|
|
690
|
+
function strokeWidthFor(shape, variant) {
|
|
691
|
+
if (variant === "color") {
|
|
692
|
+
return shape.attrs["stroke-width"];
|
|
190
693
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
694
|
+
// For native stroke shapes, keep their authored weight; otherwise use 1.8.
|
|
695
|
+
const authored = shape.attrs["stroke-width"];
|
|
696
|
+
if (typeof authored === "number")
|
|
697
|
+
return authored;
|
|
698
|
+
return variant === "outline" ? 1.8 : authored === undefined ? undefined : Number(authored);
|
|
699
|
+
}
|
|
700
|
+
// Attrs that are PAINT (resolved per variant) rather than geometry/typography.
|
|
701
|
+
const PAINT_KEYS = new Set([
|
|
702
|
+
"fill",
|
|
703
|
+
"stroke",
|
|
704
|
+
"fill-opacity",
|
|
705
|
+
"stroke-opacity",
|
|
706
|
+
"stroke-width",
|
|
707
|
+
"stroke-linecap",
|
|
708
|
+
"stroke-linejoin",
|
|
709
|
+
]);
|
|
710
|
+
/**
|
|
711
|
+
* Render one shape to an SVG element string for the given variant.
|
|
712
|
+
*
|
|
713
|
+
* `inGroup` marks a child of a <g>: the group already carries the resolved
|
|
714
|
+
* paint, so children emit ONLY geometry and INHERIT fill/stroke. This keeps
|
|
715
|
+
* grouped line-art (contactless, bank) as strokes in every variant instead of
|
|
716
|
+
* accidentally giving each child a brand fill.
|
|
717
|
+
*/
|
|
718
|
+
function renderShape(shape, variant, def, inGroup = false) {
|
|
719
|
+
const paint = inGroup ? {} : paintFor(shape, variant, def);
|
|
720
|
+
if (paint === null)
|
|
721
|
+
return "";
|
|
722
|
+
const merged = {};
|
|
723
|
+
for (const [k, v] of Object.entries(shape.attrs)) {
|
|
724
|
+
if (PAINT_KEYS.has(k))
|
|
725
|
+
continue;
|
|
726
|
+
merged[k] = v;
|
|
727
|
+
}
|
|
728
|
+
// Apply resolved paint (skipped for group children — they inherit).
|
|
729
|
+
for (const [k, v] of Object.entries(paint))
|
|
730
|
+
merged[k] = v;
|
|
731
|
+
if (!inGroup) {
|
|
732
|
+
// Stroke niceties for line shapes / mono / outline.
|
|
733
|
+
const sw = strokeWidthFor(shape, variant);
|
|
734
|
+
if (sw !== undefined && (merged.stroke !== undefined || merged.fill === "none")) {
|
|
735
|
+
merged["stroke-width"] = sw;
|
|
736
|
+
}
|
|
737
|
+
if ((variant === "outline" || (variant === "mono" && merged.stroke === "currentColor")) &&
|
|
738
|
+
merged.stroke !== undefined) {
|
|
739
|
+
merged["stroke-linecap"] = shape.attrs["stroke-linecap"] ?? "round";
|
|
740
|
+
merged["stroke-linejoin"] = shape.attrs["stroke-linejoin"] ?? "round";
|
|
741
|
+
}
|
|
742
|
+
// Carry through opacities that aren't a brand colour.
|
|
743
|
+
if (shape.attrs["fill-opacity"] !== undefined && merged.fill !== "none") {
|
|
744
|
+
merged["fill-opacity"] = shape.attrs["fill-opacity"];
|
|
745
|
+
}
|
|
746
|
+
if (shape.attrs["stroke-opacity"] !== undefined && merged.stroke !== undefined) {
|
|
747
|
+
merged["stroke-opacity"] = shape.attrs["stroke-opacity"];
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
const attrStr = Object.entries(merged)
|
|
751
|
+
.map(([k, v]) => `${k}="${typeof v === "string" ? escapeAttr(v) : v}"`)
|
|
752
|
+
.join(" ");
|
|
753
|
+
if (shape.kind === "g") {
|
|
754
|
+
const inner = (shape.children ?? []).map((c) => renderShape(c, variant, def, true)).join("");
|
|
755
|
+
return `<g ${attrStr}>${inner}</g>`;
|
|
756
|
+
}
|
|
757
|
+
if (shape.kind === "text") {
|
|
758
|
+
return `<text ${attrStr}>${escapeText(shape.text ?? "")}</text>`;
|
|
759
|
+
}
|
|
760
|
+
if (SELF_CLOSING[shape.kind]) {
|
|
761
|
+
return `<${shape.kind} ${attrStr}/>`;
|
|
762
|
+
}
|
|
763
|
+
return `<${shape.kind} ${attrStr}></${shape.kind}>`;
|
|
764
|
+
}
|
|
765
|
+
const OVERRIDES = new Map();
|
|
766
|
+
/**
|
|
767
|
+
* Register a brand's OFFICIAL, licensed SVG, overriding the bundled placeholder.
|
|
768
|
+
*
|
|
769
|
+
* Licensed users SHOULD call this to render approved official artwork:
|
|
770
|
+
* registerBrandMark("visa", officialVisaSvg); // all variants
|
|
771
|
+
* registerBrandMark("visa", { color, mono, outline }); // per-variant
|
|
772
|
+
*
|
|
773
|
+
* After registration, brandMarkSvg(name, { variant }) returns the override,
|
|
774
|
+
* falling back across variants when only some are provided
|
|
775
|
+
* (color → mono → outline, whichever exists). <npt-brand-mark> uses it too.
|
|
776
|
+
*
|
|
777
|
+
* `name` is typed as BrandMarkName | string so you may also register your own
|
|
778
|
+
* additional brands (then render them via brandMarkSvg(yourName)).
|
|
779
|
+
*/
|
|
780
|
+
export function registerBrandMark(name, svg) {
|
|
781
|
+
const entry = typeof svg === "string" ? { color: svg, mono: svg, outline: svg } : { ...svg };
|
|
782
|
+
OVERRIDES.set(name, entry);
|
|
783
|
+
}
|
|
784
|
+
/** Remove a previously registered override (mainly for tests/tooling). */
|
|
785
|
+
export function unregisterBrandMark(name) {
|
|
786
|
+
OVERRIDES.delete(name);
|
|
787
|
+
}
|
|
788
|
+
/** True when an official-asset override has been registered for `name`. */
|
|
789
|
+
export function hasBrandMarkOverride(name) {
|
|
790
|
+
return OVERRIDES.has(name);
|
|
791
|
+
}
|
|
792
|
+
/** Pick the best override string for a variant, falling back across variants. */
|
|
793
|
+
function resolveOverride(entry, variant) {
|
|
794
|
+
const order = variant === "color"
|
|
795
|
+
? ["color", "mono", "outline"]
|
|
796
|
+
: variant === "mono"
|
|
797
|
+
? ["mono", "color", "outline"]
|
|
798
|
+
: ["outline", "color", "mono"];
|
|
799
|
+
for (const v of order) {
|
|
800
|
+
const s = entry[v];
|
|
801
|
+
if (s)
|
|
802
|
+
return s;
|
|
803
|
+
}
|
|
804
|
+
return undefined;
|
|
805
|
+
}
|
|
806
|
+
/** Inject/replace width & height on an <svg> head, sized to `height` by aspect. */
|
|
807
|
+
function sizeSvg(svg, height) {
|
|
194
808
|
const vb = svg.match(/viewBox="0 0 (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)"/);
|
|
195
809
|
const vbW = vb ? Number(vb[1]) : 48;
|
|
196
810
|
const vbH = vb ? Number(vb[2]) : 32;
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
let head = svg.slice(0, svg.indexOf(">"));
|
|
811
|
+
const width = Math.round(((height * vbW) / vbH) * 100) / 100;
|
|
812
|
+
const gt = svg.indexOf(">");
|
|
813
|
+
let head = svg.slice(0, gt);
|
|
201
814
|
head = head.replace(/\s(?:width|height)="[^"]*"/g, "");
|
|
202
815
|
head += ` width="${width}" height="${height}"`;
|
|
203
|
-
return head + svg.slice(
|
|
816
|
+
return head + svg.slice(gt);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Build the complete <svg> for a placeholder mark in a given variant.
|
|
820
|
+
*/
|
|
821
|
+
function buildMarkSvg(name, variant, cls) {
|
|
822
|
+
const def = MARK_DEFS[name];
|
|
823
|
+
const [, , w, h] = def.viewBox.split(" ");
|
|
824
|
+
void w;
|
|
825
|
+
void h;
|
|
826
|
+
const body = def.shapes.map((s) => renderShape(s, variant, def)).join("");
|
|
827
|
+
const classAttr = cls ? ` class="${escapeAttr(cls)}"` : "";
|
|
828
|
+
const placeholder = def.placeholder ? ' data-placeholder="true"' : "";
|
|
829
|
+
// mono/outline silhouettes paint with currentColor — round joins are global.
|
|
830
|
+
const lineAttrs = variant === "outline"
|
|
831
|
+
? ' fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"'
|
|
832
|
+
: "";
|
|
833
|
+
return (`<svg xmlns="http://www.w3.org/2000/svg"${classAttr} viewBox="${def.viewBox}" ` +
|
|
834
|
+
`role="img" aria-label="${escapeAttr(def.label)}" ` +
|
|
835
|
+
`data-npt-brand-mark="${escapeAttr(name)}" data-variant="${variant}"${placeholder}${lineAttrs}>` +
|
|
836
|
+
`${body}</svg>`);
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Return the complete <svg> for `name`, in the requested variant, sized to a
|
|
840
|
+
* height. Aspect ratio is preserved from the mark's intrinsic viewBox.
|
|
841
|
+
*
|
|
842
|
+
* If an official asset was registered via registerBrandMark(), that override is
|
|
843
|
+
* returned (with width/height sized to `height`), falling back across variants.
|
|
844
|
+
*
|
|
845
|
+
* @throws RangeError when `name` is not a known BrandMarkName and has no override.
|
|
846
|
+
*/
|
|
847
|
+
export function brandMarkSvg(name, opts = {}) {
|
|
848
|
+
const variant = opts.variant ?? "color";
|
|
849
|
+
// 1) Official override wins.
|
|
850
|
+
const override = OVERRIDES.get(name);
|
|
851
|
+
if (override) {
|
|
852
|
+
const raw = resolveOverride(override, variant);
|
|
853
|
+
if (raw)
|
|
854
|
+
return opts.height === undefined ? raw : sizeSvg(raw, opts.height);
|
|
855
|
+
}
|
|
856
|
+
// 2) Bundled placeholder.
|
|
857
|
+
if (!isBrandMarkName(name)) {
|
|
858
|
+
throw new RangeError(`Unknown Neptune brand mark: "${String(name)}"`);
|
|
859
|
+
}
|
|
860
|
+
const svg = buildMarkSvg(name, variant, opts.class);
|
|
861
|
+
return opts.height === undefined ? svg : sizeSvg(svg, opts.height);
|
|
204
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* BRAND_MARKS — name → complete multicolour ("color" variant) <svg> string.
|
|
865
|
+
* Kept as a convenience map (back-compat). For mono/outline or overrides, use
|
|
866
|
+
* brandMarkSvg(name, { variant }).
|
|
867
|
+
*/
|
|
868
|
+
export const BRAND_MARKS = BRAND_MARK_NAMES.reduce((acc, name) => {
|
|
869
|
+
acc[name] = buildMarkSvg(name, "color");
|
|
870
|
+
return acc;
|
|
871
|
+
}, {});
|
|
205
872
|
//# sourceMappingURL=brand-marks.js.map
|