@neptune.fintech/icons 2.1.0 → 2.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/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 +908 -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,673 @@
|
|
|
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";
|
|
24
46
|
/**
|
|
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.
|
|
47
|
+
* name → mark definition. Each is an ORIGINAL simplified geometric placeholder
|
|
48
|
+
* recognisable by the brand's colours/basic forms — never traced artwork.
|
|
30
49
|
*/
|
|
31
|
-
|
|
50
|
+
const MARK_DEFS = {
|
|
32
51
|
// ── 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
|
-
|
|
52
|
+
visa: {
|
|
53
|
+
viewBox: "0 0 48 32",
|
|
54
|
+
label: "Visa",
|
|
55
|
+
colors: { a: "#1A1F71", ink: "#1A1F71" },
|
|
56
|
+
shapes: [
|
|
57
|
+
...cardFrame(),
|
|
58
|
+
{
|
|
59
|
+
kind: "text",
|
|
60
|
+
role: "a",
|
|
61
|
+
text: "VISA",
|
|
62
|
+
attrs: {
|
|
63
|
+
x: 24,
|
|
64
|
+
y: 21.5,
|
|
65
|
+
"font-family": FONT,
|
|
66
|
+
"font-size": 13,
|
|
67
|
+
"font-style": "italic",
|
|
68
|
+
"font-weight": 700,
|
|
69
|
+
"text-anchor": "middle",
|
|
70
|
+
"letter-spacing": 0.5,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
mastercard: {
|
|
76
|
+
viewBox: "0 0 48 32",
|
|
77
|
+
label: "Mastercard",
|
|
78
|
+
colors: { a: "#EB001B", b: "#F79E1B", c: "#FF5F00" },
|
|
79
|
+
shapes: [
|
|
80
|
+
...cardFrame(),
|
|
81
|
+
{ kind: "circle", role: "a", attrs: { cx: 20, cy: 16, r: 8 } },
|
|
82
|
+
{ kind: "circle", role: "b", attrs: { cx: 28, cy: 16, r: 8 } },
|
|
83
|
+
{
|
|
84
|
+
kind: "path",
|
|
85
|
+
role: "c",
|
|
86
|
+
attrs: { d: "M24 9.7a8 8 0 0 0 0 12.6 8 8 0 0 0 0-12.6Z" },
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
amex: {
|
|
91
|
+
viewBox: "0 0 48 32",
|
|
92
|
+
label: "American Express",
|
|
93
|
+
colors: { a: "#2E77BC", ink: "#FFFFFF" },
|
|
94
|
+
shapes: [
|
|
95
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
96
|
+
{
|
|
97
|
+
kind: "rect",
|
|
98
|
+
role: "ink",
|
|
99
|
+
attrs: { x: 6, y: 9, width: 36, height: 14, rx: 2, "fill-opacity": 0.12 },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
kind: "text",
|
|
103
|
+
role: "ink",
|
|
104
|
+
text: "AMEX",
|
|
105
|
+
attrs: {
|
|
106
|
+
x: 24,
|
|
107
|
+
y: 20.5,
|
|
108
|
+
"font-family": FONT,
|
|
109
|
+
"font-size": 11,
|
|
110
|
+
"font-weight": 700,
|
|
111
|
+
"text-anchor": "middle",
|
|
112
|
+
"letter-spacing": 1,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
discover: {
|
|
118
|
+
viewBox: "0 0 48 32",
|
|
119
|
+
label: "Discover",
|
|
120
|
+
colors: { ink: "#231F20", a: "#F58220" },
|
|
121
|
+
shapes: [
|
|
122
|
+
...cardFrame(),
|
|
123
|
+
{
|
|
124
|
+
kind: "text",
|
|
125
|
+
role: "ink",
|
|
126
|
+
text: "DISC",
|
|
127
|
+
attrs: {
|
|
128
|
+
x: 21,
|
|
129
|
+
y: 20.5,
|
|
130
|
+
"font-family": FONT,
|
|
131
|
+
"font-size": 9,
|
|
132
|
+
"font-weight": 700,
|
|
133
|
+
"text-anchor": "middle",
|
|
134
|
+
"letter-spacing": 0.3,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{ kind: "circle", role: "a", attrs: { cx: 36, cy: 17, r: 6 } },
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
unionpay: {
|
|
141
|
+
viewBox: "0 0 48 32",
|
|
142
|
+
label: "UnionPay",
|
|
143
|
+
colors: { a: "#E21836", b: "#00447C", c: "#007B84", ink: "#FFFFFF" },
|
|
144
|
+
shapes: [
|
|
145
|
+
...cardFrame(),
|
|
146
|
+
{ kind: "rect", role: "a", attrs: { x: 10, y: 7, width: 9, height: 18, rx: 2 } },
|
|
147
|
+
{ kind: "rect", role: "b", attrs: { x: 19, y: 7, width: 9, height: 18, rx: 2 } },
|
|
148
|
+
{ kind: "rect", role: "c", attrs: { x: 28, y: 7, width: 9, height: 18, rx: 2 } },
|
|
149
|
+
{
|
|
150
|
+
kind: "text",
|
|
151
|
+
role: "ink",
|
|
152
|
+
text: "UPAY",
|
|
153
|
+
attrs: {
|
|
154
|
+
x: 24,
|
|
155
|
+
y: 19.5,
|
|
156
|
+
"font-family": FONT,
|
|
157
|
+
"font-size": 6,
|
|
158
|
+
"font-weight": 700,
|
|
159
|
+
"text-anchor": "middle",
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
59
164
|
// ── Money transfer ──────────────────────────────────────────────────
|
|
60
|
-
"western-union":
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
165
|
+
"western-union": {
|
|
166
|
+
viewBox: "0 0 48 32",
|
|
167
|
+
label: "Western Union",
|
|
168
|
+
colors: { a: "#FFDD00", ink: "#000000" },
|
|
169
|
+
shapes: [
|
|
170
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
171
|
+
{
|
|
172
|
+
kind: "text",
|
|
173
|
+
role: "ink",
|
|
174
|
+
text: "WESTERN",
|
|
175
|
+
attrs: {
|
|
176
|
+
x: 24,
|
|
177
|
+
y: 14.5,
|
|
178
|
+
"font-family": FONT,
|
|
179
|
+
"font-size": 7,
|
|
180
|
+
"font-weight": 700,
|
|
181
|
+
"text-anchor": "middle",
|
|
182
|
+
"letter-spacing": 0.5,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
kind: "text",
|
|
187
|
+
role: "ink",
|
|
188
|
+
text: "UNION",
|
|
189
|
+
attrs: {
|
|
190
|
+
x: 24,
|
|
191
|
+
y: 23.5,
|
|
192
|
+
"font-family": FONT,
|
|
193
|
+
"font-size": 7,
|
|
194
|
+
"font-weight": 700,
|
|
195
|
+
"text-anchor": "middle",
|
|
196
|
+
"letter-spacing": 0.5,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
moneygram: {
|
|
202
|
+
viewBox: "0 0 48 32",
|
|
203
|
+
label: "MoneyGram",
|
|
204
|
+
colors: { a: "#E51937", ink: "#E51937" },
|
|
205
|
+
shapes: [
|
|
206
|
+
...cardFrame(),
|
|
207
|
+
{ kind: "circle", role: "a", attrs: { cx: 13, cy: 16, r: 5 } },
|
|
208
|
+
{
|
|
209
|
+
kind: "text",
|
|
210
|
+
role: "ink",
|
|
211
|
+
text: "MGRAM",
|
|
212
|
+
attrs: {
|
|
213
|
+
x: 29,
|
|
214
|
+
y: 19.5,
|
|
215
|
+
"font-family": FONT,
|
|
216
|
+
"font-size": 7,
|
|
217
|
+
"font-weight": 700,
|
|
218
|
+
"text-anchor": "middle",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
},
|
|
70
223
|
// ── 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
|
-
|
|
224
|
+
numo: {
|
|
225
|
+
viewBox: "0 0 48 32",
|
|
226
|
+
label: "NUMO (placeholder mark)",
|
|
227
|
+
placeholder: true,
|
|
228
|
+
colors: { a: "#0E2A47", b: "#5AA9E6", ink: "#FFFFFF" },
|
|
229
|
+
shapes: [
|
|
230
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
231
|
+
{
|
|
232
|
+
kind: "rect",
|
|
233
|
+
role: "b",
|
|
234
|
+
attrs: { x: 6, y: 8, width: 36, height: 16, rx: 3, fill: "none", "stroke-width": 1.4 },
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
kind: "text",
|
|
238
|
+
role: "ink",
|
|
239
|
+
text: "NUMO",
|
|
240
|
+
attrs: {
|
|
241
|
+
x: 24,
|
|
242
|
+
y: 21,
|
|
243
|
+
"font-family": FONT,
|
|
244
|
+
"font-size": 9,
|
|
245
|
+
"font-weight": 700,
|
|
246
|
+
"text-anchor": "middle",
|
|
247
|
+
"letter-spacing": 1.5,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
moamalat: {
|
|
253
|
+
viewBox: "0 0 48 32",
|
|
254
|
+
label: "Moamalat (placeholder mark)",
|
|
255
|
+
placeholder: true,
|
|
256
|
+
colors: { a: "#1C7A4D", b: "#FFFFFF", ink: "#FFFFFF" },
|
|
257
|
+
shapes: [
|
|
258
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
259
|
+
{
|
|
260
|
+
kind: "circle",
|
|
261
|
+
role: "b",
|
|
262
|
+
attrs: { cx: 13, cy: 16, r: 6, fill: "none", "stroke-width": 1.4 },
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
kind: "text",
|
|
266
|
+
role: "ink",
|
|
267
|
+
text: "M",
|
|
268
|
+
attrs: {
|
|
269
|
+
x: 13,
|
|
270
|
+
y: 19,
|
|
271
|
+
"font-family": FONT,
|
|
272
|
+
"font-size": 8,
|
|
273
|
+
"font-weight": 700,
|
|
274
|
+
"text-anchor": "middle",
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
kind: "text",
|
|
279
|
+
role: "ink",
|
|
280
|
+
text: "MOAM",
|
|
281
|
+
attrs: {
|
|
282
|
+
x: 31,
|
|
283
|
+
y: 19.5,
|
|
284
|
+
"font-family": FONT,
|
|
285
|
+
"font-size": 7,
|
|
286
|
+
"font-weight": 700,
|
|
287
|
+
"text-anchor": "middle",
|
|
288
|
+
"letter-spacing": 0.5,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
// LyPay — a green→blue swoosh/flag beside a "LyPay" wordmark block.
|
|
294
|
+
// Original geometry: two short strokes (green, blue) form a flag, wordmark right.
|
|
295
|
+
lypay: {
|
|
296
|
+
viewBox: "0 0 48 32",
|
|
297
|
+
label: "LyPay (placeholder mark)",
|
|
298
|
+
placeholder: true,
|
|
299
|
+
colors: { a: "#3FBF7F", b: "#2AA0D8", ink: "#0E3A2E" },
|
|
300
|
+
shapes: [
|
|
301
|
+
...cardFrame("#EAF7F0"),
|
|
302
|
+
// green swoosh stroke
|
|
303
|
+
{
|
|
304
|
+
kind: "path",
|
|
305
|
+
role: "a",
|
|
306
|
+
attrs: {
|
|
307
|
+
d: "M7 21c3-6 6-9 11-9",
|
|
308
|
+
fill: "none",
|
|
309
|
+
"stroke-width": 2.6,
|
|
310
|
+
"stroke-linecap": "round",
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
// blue swoosh stroke (above the green), a flag
|
|
314
|
+
{
|
|
315
|
+
kind: "path",
|
|
316
|
+
role: "b",
|
|
317
|
+
attrs: {
|
|
318
|
+
d: "M7 16c3-5 6-7.5 11-7.5",
|
|
319
|
+
fill: "none",
|
|
320
|
+
"stroke-width": 2.6,
|
|
321
|
+
"stroke-linecap": "round",
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
kind: "text",
|
|
326
|
+
role: "ink",
|
|
327
|
+
text: "LyPay",
|
|
328
|
+
attrs: {
|
|
329
|
+
x: 33,
|
|
330
|
+
y: 20,
|
|
331
|
+
"font-family": FONT,
|
|
332
|
+
"font-size": 9,
|
|
333
|
+
"font-weight": 700,
|
|
334
|
+
"text-anchor": "middle",
|
|
335
|
+
"letter-spacing": 0.3,
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
// OnePay — a deep-blue rounded tile with a stylised folded "1" ribbon motif.
|
|
341
|
+
// Original geometry: rounded tile (deep blue), a folded "1" from two strokes
|
|
342
|
+
// in a lighter blue, plus a small "PAY" wordmark.
|
|
343
|
+
onepay: {
|
|
344
|
+
viewBox: "0 0 48 32",
|
|
345
|
+
label: "OnePay (placeholder mark)",
|
|
346
|
+
placeholder: true,
|
|
347
|
+
colors: { a: "#1F6FB2", b: "#2AA0D8", ink: "#FFFFFF" },
|
|
348
|
+
shapes: [
|
|
349
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 6 } },
|
|
350
|
+
// folded "1": a flag-foot stroke + the upright, in the lighter blue tone.
|
|
351
|
+
{
|
|
352
|
+
kind: "path",
|
|
353
|
+
role: "b",
|
|
354
|
+
attrs: {
|
|
355
|
+
d: "M10 11.5l4-2.5v14",
|
|
356
|
+
fill: "none",
|
|
357
|
+
"stroke-width": 2.6,
|
|
358
|
+
"stroke-linecap": "round",
|
|
359
|
+
"stroke-linejoin": "round",
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
kind: "path",
|
|
364
|
+
role: "b",
|
|
365
|
+
attrs: {
|
|
366
|
+
d: "M11 23h6",
|
|
367
|
+
fill: "none",
|
|
368
|
+
"stroke-width": 2.6,
|
|
369
|
+
"stroke-linecap": "round",
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
kind: "text",
|
|
374
|
+
role: "ink",
|
|
375
|
+
text: "PAY",
|
|
376
|
+
attrs: {
|
|
377
|
+
x: 32,
|
|
378
|
+
y: 19.5,
|
|
379
|
+
"font-family": FONT,
|
|
380
|
+
"font-size": 8,
|
|
381
|
+
"font-weight": 700,
|
|
382
|
+
"text-anchor": "middle",
|
|
383
|
+
"letter-spacing": 0.5,
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
},
|
|
388
|
+
sadad: {
|
|
389
|
+
viewBox: "0 0 48 32",
|
|
390
|
+
label: "Sadad (placeholder mark)",
|
|
391
|
+
placeholder: true,
|
|
392
|
+
colors: { a: "#5B2E91", b: "#FFFFFF", ink: "#FFFFFF" },
|
|
393
|
+
shapes: [
|
|
394
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
395
|
+
{
|
|
396
|
+
kind: "rect",
|
|
397
|
+
role: "b",
|
|
398
|
+
attrs: { x: 7, y: 9, width: 34, height: 14, rx: 3, fill: "none", "stroke-width": 1.4 },
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
kind: "text",
|
|
402
|
+
role: "ink",
|
|
403
|
+
text: "SADAD",
|
|
404
|
+
attrs: {
|
|
405
|
+
x: 24,
|
|
406
|
+
y: 20.5,
|
|
407
|
+
"font-family": FONT,
|
|
408
|
+
"font-size": 9,
|
|
409
|
+
"font-weight": 700,
|
|
410
|
+
"text-anchor": "middle",
|
|
411
|
+
"letter-spacing": 1,
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
},
|
|
416
|
+
tadawul: {
|
|
417
|
+
viewBox: "0 0 48 32",
|
|
418
|
+
label: "Tadawul (placeholder mark)",
|
|
419
|
+
placeholder: true,
|
|
420
|
+
colors: { a: "#1A4D4D", b: "#3FC1C9", ink: "#FFFFFF" },
|
|
421
|
+
shapes: [
|
|
422
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
423
|
+
{
|
|
424
|
+
kind: "path",
|
|
425
|
+
role: "b",
|
|
426
|
+
attrs: {
|
|
427
|
+
d: "M9 20l5-5 4 3 6-7",
|
|
428
|
+
fill: "none",
|
|
429
|
+
"stroke-width": 1.6,
|
|
430
|
+
"stroke-linecap": "round",
|
|
431
|
+
"stroke-linejoin": "round",
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
kind: "text",
|
|
436
|
+
role: "ink",
|
|
437
|
+
text: "TDWL",
|
|
438
|
+
attrs: {
|
|
439
|
+
x: 33,
|
|
440
|
+
y: 19.5,
|
|
441
|
+
"font-family": FONT,
|
|
442
|
+
"font-size": 6,
|
|
443
|
+
"font-weight": 700,
|
|
444
|
+
"text-anchor": "middle",
|
|
445
|
+
"letter-spacing": 0.5,
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
],
|
|
449
|
+
},
|
|
103
450
|
// ── 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
|
-
|
|
451
|
+
"apple-pay": {
|
|
452
|
+
viewBox: "0 0 48 32",
|
|
453
|
+
label: "Apple Pay",
|
|
454
|
+
colors: { ink: "#000000" },
|
|
455
|
+
shapes: [
|
|
456
|
+
...cardFrame("#FFFFFF"),
|
|
457
|
+
{
|
|
458
|
+
kind: "path",
|
|
459
|
+
role: "ink",
|
|
460
|
+
attrs: {
|
|
461
|
+
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",
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
kind: "text",
|
|
466
|
+
role: "ink",
|
|
467
|
+
text: "Pay",
|
|
468
|
+
attrs: {
|
|
469
|
+
x: 33,
|
|
470
|
+
y: 19.5,
|
|
471
|
+
"font-family": FONT,
|
|
472
|
+
"font-size": 9,
|
|
473
|
+
"font-weight": 600,
|
|
474
|
+
"text-anchor": "middle",
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
},
|
|
479
|
+
"google-pay": {
|
|
480
|
+
viewBox: "0 0 48 32",
|
|
481
|
+
label: "Google Pay",
|
|
482
|
+
colors: { a: "#4285F4", b: "#34A853", ink: "#5F6368" },
|
|
483
|
+
shapes: [
|
|
484
|
+
...cardFrame("#FFFFFF"),
|
|
485
|
+
{
|
|
486
|
+
kind: "path",
|
|
487
|
+
role: "a",
|
|
488
|
+
attrs: {
|
|
489
|
+
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",
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
kind: "path",
|
|
494
|
+
role: "b",
|
|
495
|
+
attrs: { d: "M19 16.2h-3v2.1h3a3 3 0 0 0 .1-.8 4 4 0 0 0-.1-1.3Z" },
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
kind: "text",
|
|
499
|
+
role: "ink",
|
|
500
|
+
text: "Pay",
|
|
501
|
+
attrs: {
|
|
502
|
+
x: 33,
|
|
503
|
+
y: 19.5,
|
|
504
|
+
"font-family": FONT,
|
|
505
|
+
"font-size": 9,
|
|
506
|
+
"font-weight": 600,
|
|
507
|
+
"text-anchor": "middle",
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
paypal: {
|
|
513
|
+
viewBox: "0 0 48 32",
|
|
514
|
+
label: "PayPal",
|
|
515
|
+
colors: { a: "#003087", b: "#009CDE", ink: "#003087" },
|
|
516
|
+
shapes: [
|
|
517
|
+
...cardFrame(),
|
|
518
|
+
{
|
|
519
|
+
kind: "path",
|
|
520
|
+
role: "a",
|
|
521
|
+
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" },
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
kind: "path",
|
|
525
|
+
role: "b",
|
|
526
|
+
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" },
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
kind: "text",
|
|
530
|
+
role: "ink",
|
|
531
|
+
text: "Pal",
|
|
532
|
+
attrs: {
|
|
533
|
+
x: 34,
|
|
534
|
+
y: 20,
|
|
535
|
+
"font-family": FONT,
|
|
536
|
+
"font-size": 7,
|
|
537
|
+
"font-weight": 700,
|
|
538
|
+
"text-anchor": "middle",
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
},
|
|
543
|
+
swift: {
|
|
544
|
+
viewBox: "0 0 48 32",
|
|
545
|
+
label: "SWIFT",
|
|
546
|
+
colors: { a: "#0033A0", ink: "#FFFFFF" },
|
|
547
|
+
shapes: [
|
|
548
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
549
|
+
{
|
|
550
|
+
kind: "text",
|
|
551
|
+
role: "ink",
|
|
552
|
+
text: "SWIFT",
|
|
553
|
+
attrs: {
|
|
554
|
+
x: 24,
|
|
555
|
+
y: 20.5,
|
|
556
|
+
"font-family": FONT,
|
|
557
|
+
"font-size": 10,
|
|
558
|
+
"font-weight": 700,
|
|
559
|
+
"text-anchor": "middle",
|
|
560
|
+
"letter-spacing": 1.5,
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
],
|
|
564
|
+
},
|
|
565
|
+
mada: {
|
|
566
|
+
viewBox: "0 0 48 32",
|
|
567
|
+
label: "mada-style domestic scheme (generic)",
|
|
568
|
+
placeholder: true,
|
|
569
|
+
colors: { a: "#84BD00", b: "#1F3661" },
|
|
570
|
+
shapes: [
|
|
571
|
+
...cardFrame(),
|
|
572
|
+
{ kind: "rect", role: "a", attrs: { x: 9, y: 13, width: 14, height: 6, rx: 3 } },
|
|
573
|
+
{ kind: "rect", role: "b", attrs: { x: 25, y: 13, width: 14, height: 6, rx: 3 } },
|
|
574
|
+
],
|
|
575
|
+
},
|
|
576
|
+
"generic-card": {
|
|
577
|
+
viewBox: "0 0 48 32",
|
|
578
|
+
label: "Card",
|
|
579
|
+
colors: { a: "#3C4858", b: "#E8C56B", ink: "#FFFFFF" },
|
|
580
|
+
shapes: [
|
|
581
|
+
{ kind: "rect", role: "a", attrs: { width: 48, height: 32, rx: 4 } },
|
|
582
|
+
{ kind: "rect", role: "b", attrs: { x: 6, y: 11, width: 8, height: 6, rx: 1.2 } },
|
|
583
|
+
{
|
|
584
|
+
kind: "path",
|
|
585
|
+
role: "ink",
|
|
586
|
+
attrs: {
|
|
587
|
+
d: "M6 22h20",
|
|
588
|
+
fill: "none",
|
|
589
|
+
"stroke-width": 1.4,
|
|
590
|
+
"stroke-linecap": "round",
|
|
591
|
+
"stroke-opacity": 0.7,
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
kind: "path",
|
|
596
|
+
role: "ink",
|
|
597
|
+
attrs: {
|
|
598
|
+
d: "M30 22h12",
|
|
599
|
+
fill: "none",
|
|
600
|
+
"stroke-width": 1.4,
|
|
601
|
+
"stroke-linecap": "round",
|
|
602
|
+
"stroke-opacity": 0.4,
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
],
|
|
606
|
+
},
|
|
607
|
+
"contactless-pay": {
|
|
608
|
+
viewBox: "0 0 48 32",
|
|
609
|
+
label: "Contactless payment",
|
|
610
|
+
colors: { a: "#1F6FEB" },
|
|
611
|
+
shapes: [
|
|
612
|
+
...cardFrame(),
|
|
613
|
+
{
|
|
614
|
+
kind: "g",
|
|
615
|
+
role: "a",
|
|
616
|
+
attrs: { fill: "none", "stroke-width": 1.8, "stroke-linecap": "round" },
|
|
617
|
+
children: [
|
|
618
|
+
{ kind: "path", role: "a", attrs: { d: "M18 12a8 8 0 0 1 0 8" } },
|
|
619
|
+
{ kind: "path", role: "a", attrs: { d: "M22 9.5a12 12 0 0 1 0 13" } },
|
|
620
|
+
{ kind: "path", role: "a", attrs: { d: "M26 7.5a16 16 0 0 1 0 17" } },
|
|
621
|
+
],
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
},
|
|
625
|
+
cash: {
|
|
626
|
+
viewBox: "0 0 48 32",
|
|
627
|
+
label: "Cash",
|
|
628
|
+
colors: { a: "#2E7D32", b: "#A5D6A7", ink: "#A5D6A7" },
|
|
629
|
+
shapes: [
|
|
630
|
+
...cardFrame("#E8F5E9"),
|
|
631
|
+
{ kind: "rect", role: "a", attrs: { x: 9, y: 9, width: 30, height: 14, rx: 2 } },
|
|
632
|
+
{
|
|
633
|
+
kind: "circle",
|
|
634
|
+
role: "b",
|
|
635
|
+
attrs: { cx: 24, cy: 16, r: 4, fill: "none", "stroke-width": 1.4 },
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
kind: "text",
|
|
639
|
+
role: "ink",
|
|
640
|
+
text: "$",
|
|
641
|
+
attrs: {
|
|
642
|
+
x: 24,
|
|
643
|
+
y: 18.5,
|
|
644
|
+
"font-family": FONT,
|
|
645
|
+
"font-size": 6,
|
|
646
|
+
"font-weight": 700,
|
|
647
|
+
"text-anchor": "middle",
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
],
|
|
651
|
+
},
|
|
652
|
+
"bank-building": {
|
|
653
|
+
viewBox: "0 0 48 32",
|
|
654
|
+
label: "Bank",
|
|
655
|
+
colors: { a: "#2A3A5A" },
|
|
656
|
+
shapes: [
|
|
657
|
+
...cardFrame("#EEF1F6"),
|
|
658
|
+
{
|
|
659
|
+
kind: "g",
|
|
660
|
+
role: "a",
|
|
661
|
+
attrs: {
|
|
662
|
+
fill: "none",
|
|
663
|
+
"stroke-width": 1.6,
|
|
664
|
+
"stroke-linecap": "round",
|
|
665
|
+
"stroke-linejoin": "round",
|
|
666
|
+
},
|
|
667
|
+
children: [
|
|
668
|
+
{ kind: "path", role: "a", attrs: { d: "M14 14l10-5 10 5" } },
|
|
669
|
+
{ kind: "path", role: "a", attrs: { d: "M16 14v7" } },
|
|
670
|
+
{ kind: "path", role: "a", attrs: { d: "M21 14v7" } },
|
|
671
|
+
{ kind: "path", role: "a", attrs: { d: "M27 14v7" } },
|
|
672
|
+
{ kind: "path", role: "a", attrs: { d: "M32 14v7" } },
|
|
673
|
+
{ kind: "path", role: "a", attrs: { d: "M13 23h22" } },
|
|
674
|
+
],
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
},
|
|
152
678
|
};
|
|
153
679
|
/** All brand-mark names, in catalogue order. */
|
|
154
680
|
export const BRAND_MARK_NAMES = [
|
|
@@ -177,29 +703,265 @@ export const BRAND_MARK_NAMES = [
|
|
|
177
703
|
];
|
|
178
704
|
/** True when `name` is a known brand mark. Acts as a type guard. */
|
|
179
705
|
export function isBrandMarkName(name) {
|
|
180
|
-
return Object.prototype.hasOwnProperty.call(
|
|
706
|
+
return Object.prototype.hasOwnProperty.call(MARK_DEFS, name);
|
|
707
|
+
}
|
|
708
|
+
// ── Rendering ───────────────────────────────────────────────────────────────
|
|
709
|
+
const escapeAttr = (v) => v.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
710
|
+
const escapeText = (v) => v.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
711
|
+
/** The single SVG tag for a shape kind ('g' has children, 'text' has a body). */
|
|
712
|
+
const SELF_CLOSING = {
|
|
713
|
+
rect: true,
|
|
714
|
+
circle: true,
|
|
715
|
+
ellipse: true,
|
|
716
|
+
path: true,
|
|
717
|
+
line: true,
|
|
718
|
+
text: false,
|
|
719
|
+
g: false,
|
|
720
|
+
};
|
|
721
|
+
/** A rect that fills the whole canvas — a coloured card body / backdrop. */
|
|
722
|
+
function isFullBleedBackdrop(shape, def) {
|
|
723
|
+
if (shape.kind !== "rect")
|
|
724
|
+
return false;
|
|
725
|
+
const [, , vbW, vbH] = def.viewBox.split(" ").map(Number);
|
|
726
|
+
const w = Number(shape.attrs.width);
|
|
727
|
+
const h = Number(shape.attrs.height);
|
|
728
|
+
// Anchored at the origin (no x/y, or 0) and spanning the full viewBox.
|
|
729
|
+
const x = Number(shape.attrs.x ?? 0);
|
|
730
|
+
const y = Number(shape.attrs.y ?? 0);
|
|
731
|
+
return x === 0 && y === 0 && w === vbW && h === vbH;
|
|
181
732
|
}
|
|
182
733
|
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
734
|
+
* Resolve the paint attributes a shape gets in a given variant.
|
|
735
|
+
* - color → brand colour by role; strokes (fill:none in attrs) keep stroke=colour.
|
|
736
|
+
* - mono → everything is a flat `currentColor` silhouette (fill); neutral
|
|
737
|
+
* frames AND full-bleed coloured backdrops are dropped so the inked
|
|
738
|
+
* glyph reads as the silhouette (e.g. AMEX/SWIFT lettering).
|
|
739
|
+
* - outline → stroke="currentColor", fill="none", round joins; frames/backdrops
|
|
740
|
+
* dropped.
|
|
741
|
+
* Returns null when the shape should be omitted in this variant.
|
|
186
742
|
*/
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
743
|
+
function paintFor(shape, variant, def) {
|
|
744
|
+
const isStrokeShape = shape.attrs.fill === "none" || shape.attrs["stroke-width"] !== undefined;
|
|
745
|
+
const isFrame = shape.role === "bg" || shape.role === "hairline";
|
|
746
|
+
if (variant === "color") {
|
|
747
|
+
const out = {};
|
|
748
|
+
if (isFrame) {
|
|
749
|
+
// Neutral frame keeps its literal colours (bg fill / hairline stroke).
|
|
750
|
+
if (shape.role === "bg") {
|
|
751
|
+
out.fill = shape.attrs.fill ?? CARD_BG;
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
out.fill = "none";
|
|
755
|
+
out.stroke = CARD_HAIRLINE;
|
|
756
|
+
}
|
|
757
|
+
return out;
|
|
758
|
+
}
|
|
759
|
+
const brand = def.colors[shape.role] ?? shape.attrs.fill ?? "#000000";
|
|
760
|
+
if (isStrokeShape) {
|
|
761
|
+
out.fill = "none";
|
|
762
|
+
out.stroke = brand;
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
out.fill = brand;
|
|
766
|
+
}
|
|
767
|
+
return out;
|
|
768
|
+
}
|
|
769
|
+
// mono + outline: drop the neutral frame and any full-bleed coloured backdrop,
|
|
770
|
+
// so the foreground glyph (text/paths) becomes the silhouette.
|
|
771
|
+
if (isFrame || isFullBleedBackdrop(shape, def))
|
|
772
|
+
return null;
|
|
773
|
+
if (variant === "mono") {
|
|
774
|
+
// Flatten to a single currentColor silhouette. Stroke shapes stay strokes
|
|
775
|
+
// (so line-art marks like contactless/bank still read), filled shapes fill.
|
|
776
|
+
if (isStrokeShape) {
|
|
777
|
+
return { fill: "none", stroke: "currentColor" };
|
|
778
|
+
}
|
|
779
|
+
return { fill: "currentColor" };
|
|
780
|
+
}
|
|
781
|
+
// outline
|
|
782
|
+
return { fill: "none", stroke: "currentColor" };
|
|
783
|
+
}
|
|
784
|
+
/** Stroke-width to use for a shape in mono/outline (keeps line marks tidy). */
|
|
785
|
+
function strokeWidthFor(shape, variant) {
|
|
786
|
+
if (variant === "color") {
|
|
787
|
+
return shape.attrs["stroke-width"];
|
|
190
788
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
789
|
+
// For native stroke shapes, keep their authored weight; otherwise use 1.8.
|
|
790
|
+
const authored = shape.attrs["stroke-width"];
|
|
791
|
+
if (typeof authored === "number")
|
|
792
|
+
return authored;
|
|
793
|
+
return variant === "outline" ? 1.8 : authored === undefined ? undefined : Number(authored);
|
|
794
|
+
}
|
|
795
|
+
// Attrs that are PAINT (resolved per variant) rather than geometry/typography.
|
|
796
|
+
const PAINT_KEYS = new Set([
|
|
797
|
+
"fill",
|
|
798
|
+
"stroke",
|
|
799
|
+
"fill-opacity",
|
|
800
|
+
"stroke-opacity",
|
|
801
|
+
"stroke-width",
|
|
802
|
+
"stroke-linecap",
|
|
803
|
+
"stroke-linejoin",
|
|
804
|
+
]);
|
|
805
|
+
/**
|
|
806
|
+
* Render one shape to an SVG element string for the given variant.
|
|
807
|
+
*
|
|
808
|
+
* `inGroup` marks a child of a <g>: the group already carries the resolved
|
|
809
|
+
* paint, so children emit ONLY geometry and INHERIT fill/stroke. This keeps
|
|
810
|
+
* grouped line-art (contactless, bank) as strokes in every variant instead of
|
|
811
|
+
* accidentally giving each child a brand fill.
|
|
812
|
+
*/
|
|
813
|
+
function renderShape(shape, variant, def, inGroup = false) {
|
|
814
|
+
const paint = inGroup ? {} : paintFor(shape, variant, def);
|
|
815
|
+
if (paint === null)
|
|
816
|
+
return "";
|
|
817
|
+
const merged = {};
|
|
818
|
+
for (const [k, v] of Object.entries(shape.attrs)) {
|
|
819
|
+
if (PAINT_KEYS.has(k))
|
|
820
|
+
continue;
|
|
821
|
+
merged[k] = v;
|
|
822
|
+
}
|
|
823
|
+
// Apply resolved paint (skipped for group children — they inherit).
|
|
824
|
+
for (const [k, v] of Object.entries(paint))
|
|
825
|
+
merged[k] = v;
|
|
826
|
+
if (!inGroup) {
|
|
827
|
+
// Stroke niceties for line shapes / mono / outline.
|
|
828
|
+
const sw = strokeWidthFor(shape, variant);
|
|
829
|
+
if (sw !== undefined && (merged.stroke !== undefined || merged.fill === "none")) {
|
|
830
|
+
merged["stroke-width"] = sw;
|
|
831
|
+
}
|
|
832
|
+
if ((variant === "outline" || (variant === "mono" && merged.stroke === "currentColor")) &&
|
|
833
|
+
merged.stroke !== undefined) {
|
|
834
|
+
merged["stroke-linecap"] = shape.attrs["stroke-linecap"] ?? "round";
|
|
835
|
+
merged["stroke-linejoin"] = shape.attrs["stroke-linejoin"] ?? "round";
|
|
836
|
+
}
|
|
837
|
+
// Carry through opacities that aren't a brand colour.
|
|
838
|
+
if (shape.attrs["fill-opacity"] !== undefined && merged.fill !== "none") {
|
|
839
|
+
merged["fill-opacity"] = shape.attrs["fill-opacity"];
|
|
840
|
+
}
|
|
841
|
+
if (shape.attrs["stroke-opacity"] !== undefined && merged.stroke !== undefined) {
|
|
842
|
+
merged["stroke-opacity"] = shape.attrs["stroke-opacity"];
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
const attrStr = Object.entries(merged)
|
|
846
|
+
.map(([k, v]) => `${k}="${typeof v === "string" ? escapeAttr(v) : v}"`)
|
|
847
|
+
.join(" ");
|
|
848
|
+
if (shape.kind === "g") {
|
|
849
|
+
const inner = (shape.children ?? []).map((c) => renderShape(c, variant, def, true)).join("");
|
|
850
|
+
return `<g ${attrStr}>${inner}</g>`;
|
|
851
|
+
}
|
|
852
|
+
if (shape.kind === "text") {
|
|
853
|
+
return `<text ${attrStr}>${escapeText(shape.text ?? "")}</text>`;
|
|
854
|
+
}
|
|
855
|
+
if (SELF_CLOSING[shape.kind]) {
|
|
856
|
+
return `<${shape.kind} ${attrStr}/>`;
|
|
857
|
+
}
|
|
858
|
+
return `<${shape.kind} ${attrStr}></${shape.kind}>`;
|
|
859
|
+
}
|
|
860
|
+
const OVERRIDES = new Map();
|
|
861
|
+
/**
|
|
862
|
+
* Register a brand's OFFICIAL, licensed SVG, overriding the bundled placeholder.
|
|
863
|
+
*
|
|
864
|
+
* Licensed users SHOULD call this to render approved official artwork:
|
|
865
|
+
* registerBrandMark("visa", officialVisaSvg); // all variants
|
|
866
|
+
* registerBrandMark("visa", { color, mono, outline }); // per-variant
|
|
867
|
+
*
|
|
868
|
+
* After registration, brandMarkSvg(name, { variant }) returns the override,
|
|
869
|
+
* falling back across variants when only some are provided
|
|
870
|
+
* (color → mono → outline, whichever exists). <npt-brand-mark> uses it too.
|
|
871
|
+
*
|
|
872
|
+
* `name` is typed as BrandMarkName | string so you may also register your own
|
|
873
|
+
* additional brands (then render them via brandMarkSvg(yourName)).
|
|
874
|
+
*/
|
|
875
|
+
export function registerBrandMark(name, svg) {
|
|
876
|
+
const entry = typeof svg === "string" ? { color: svg, mono: svg, outline: svg } : { ...svg };
|
|
877
|
+
OVERRIDES.set(name, entry);
|
|
878
|
+
}
|
|
879
|
+
/** Remove a previously registered override (mainly for tests/tooling). */
|
|
880
|
+
export function unregisterBrandMark(name) {
|
|
881
|
+
OVERRIDES.delete(name);
|
|
882
|
+
}
|
|
883
|
+
/** True when an official-asset override has been registered for `name`. */
|
|
884
|
+
export function hasBrandMarkOverride(name) {
|
|
885
|
+
return OVERRIDES.has(name);
|
|
886
|
+
}
|
|
887
|
+
/** Pick the best override string for a variant, falling back across variants. */
|
|
888
|
+
function resolveOverride(entry, variant) {
|
|
889
|
+
const order = variant === "color"
|
|
890
|
+
? ["color", "mono", "outline"]
|
|
891
|
+
: variant === "mono"
|
|
892
|
+
? ["mono", "color", "outline"]
|
|
893
|
+
: ["outline", "color", "mono"];
|
|
894
|
+
for (const v of order) {
|
|
895
|
+
const s = entry[v];
|
|
896
|
+
if (s)
|
|
897
|
+
return s;
|
|
898
|
+
}
|
|
899
|
+
return undefined;
|
|
900
|
+
}
|
|
901
|
+
/** Inject/replace width & height on an <svg> head, sized to `height` by aspect. */
|
|
902
|
+
function sizeSvg(svg, height) {
|
|
194
903
|
const vb = svg.match(/viewBox="0 0 (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)"/);
|
|
195
904
|
const vbW = vb ? Number(vb[1]) : 48;
|
|
196
905
|
const vbH = vb ? Number(vb[2]) : 32;
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
let head = svg.slice(0, svg.indexOf(">"));
|
|
906
|
+
const width = Math.round(((height * vbW) / vbH) * 100) / 100;
|
|
907
|
+
const gt = svg.indexOf(">");
|
|
908
|
+
let head = svg.slice(0, gt);
|
|
201
909
|
head = head.replace(/\s(?:width|height)="[^"]*"/g, "");
|
|
202
910
|
head += ` width="${width}" height="${height}"`;
|
|
203
|
-
return head + svg.slice(
|
|
911
|
+
return head + svg.slice(gt);
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Build the complete <svg> for a placeholder mark in a given variant.
|
|
915
|
+
*/
|
|
916
|
+
function buildMarkSvg(name, variant, cls) {
|
|
917
|
+
const def = MARK_DEFS[name];
|
|
918
|
+
const [, , w, h] = def.viewBox.split(" ");
|
|
919
|
+
void w;
|
|
920
|
+
void h;
|
|
921
|
+
const body = def.shapes.map((s) => renderShape(s, variant, def)).join("");
|
|
922
|
+
const classAttr = cls ? ` class="${escapeAttr(cls)}"` : "";
|
|
923
|
+
const placeholder = def.placeholder ? ' data-placeholder="true"' : "";
|
|
924
|
+
// mono/outline silhouettes paint with currentColor — round joins are global.
|
|
925
|
+
const lineAttrs = variant === "outline"
|
|
926
|
+
? ' fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"'
|
|
927
|
+
: "";
|
|
928
|
+
return (`<svg xmlns="http://www.w3.org/2000/svg"${classAttr} viewBox="${def.viewBox}" ` +
|
|
929
|
+
`role="img" aria-label="${escapeAttr(def.label)}" ` +
|
|
930
|
+
`data-npt-brand-mark="${escapeAttr(name)}" data-variant="${variant}"${placeholder}${lineAttrs}>` +
|
|
931
|
+
`${body}</svg>`);
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Return the complete <svg> for `name`, in the requested variant, sized to a
|
|
935
|
+
* height. Aspect ratio is preserved from the mark's intrinsic viewBox.
|
|
936
|
+
*
|
|
937
|
+
* If an official asset was registered via registerBrandMark(), that override is
|
|
938
|
+
* returned (with width/height sized to `height`), falling back across variants.
|
|
939
|
+
*
|
|
940
|
+
* @throws RangeError when `name` is not a known BrandMarkName and has no override.
|
|
941
|
+
*/
|
|
942
|
+
export function brandMarkSvg(name, opts = {}) {
|
|
943
|
+
const variant = opts.variant ?? "color";
|
|
944
|
+
// 1) Official override wins.
|
|
945
|
+
const override = OVERRIDES.get(name);
|
|
946
|
+
if (override) {
|
|
947
|
+
const raw = resolveOverride(override, variant);
|
|
948
|
+
if (raw)
|
|
949
|
+
return opts.height === undefined ? raw : sizeSvg(raw, opts.height);
|
|
950
|
+
}
|
|
951
|
+
// 2) Bundled placeholder.
|
|
952
|
+
if (!isBrandMarkName(name)) {
|
|
953
|
+
throw new RangeError(`Unknown Neptune brand mark: "${String(name)}"`);
|
|
954
|
+
}
|
|
955
|
+
const svg = buildMarkSvg(name, variant, opts.class);
|
|
956
|
+
return opts.height === undefined ? svg : sizeSvg(svg, opts.height);
|
|
204
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* BRAND_MARKS — name → complete multicolour ("color" variant) <svg> string.
|
|
960
|
+
* Kept as a convenience map (back-compat). For mono/outline or overrides, use
|
|
961
|
+
* brandMarkSvg(name, { variant }).
|
|
962
|
+
*/
|
|
963
|
+
export const BRAND_MARKS = BRAND_MARK_NAMES.reduce((acc, name) => {
|
|
964
|
+
acc[name] = buildMarkSvg(name, "color");
|
|
965
|
+
return acc;
|
|
966
|
+
}, {});
|
|
205
967
|
//# sourceMappingURL=brand-marks.js.map
|