@port60/template-kit 0.1.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.
@@ -0,0 +1,534 @@
1
+ {
2
+ "description": "The data context templates render with. Templates receive ONLY platform-provided data — never secrets, never other tenants' data. Additive-only within a contract major. Section renderers get {section, brand}; the layout gets {brand, nav, socials}; page templates (pages/{page}.liquid) get their page's documented objects + brand.",
3
+ "variables": [
4
+ {
5
+ "path": "section",
6
+ "availableIn": [
7
+ "section"
8
+ ],
9
+ "description": "The typed content of the section being rendered — its shape is the section type's fields (see the section catalogue)."
10
+ },
11
+ {
12
+ "path": "brand.name",
13
+ "availableIn": [
14
+ "section",
15
+ "layout"
16
+ ],
17
+ "description": "The charity's display name."
18
+ },
19
+ {
20
+ "path": "brand.tagline",
21
+ "availableIn": [
22
+ "section",
23
+ "layout"
24
+ ],
25
+ "description": "The charity's tagline."
26
+ },
27
+ {
28
+ "path": "brand.logoUrl",
29
+ "availableIn": [
30
+ "layout"
31
+ ],
32
+ "description": "The charity's logo URL, or null when none is set — always branch on it."
33
+ },
34
+ {
35
+ "path": "brand.logoType",
36
+ "availableIn": [
37
+ "layout"
38
+ ],
39
+ "description": "'full' (the image already contains the name — show it alone) or 'icon' (a mark — show it beside brand.name)."
40
+ },
41
+ {
42
+ "path": "nav.items",
43
+ "availableIn": [
44
+ "layout"
45
+ ],
46
+ "description": "The site's primary navigation, platform-derived per tenant (pages that exist, features that are on). Render ALL of it — items a tenant doesn't have simply aren't in the list."
47
+ },
48
+ {
49
+ "path": "nav.items[].label",
50
+ "availableIn": [
51
+ "layout"
52
+ ],
53
+ "description": "The item's display label (tenant-chosen for some items — treat as text, it is escaped like everything else)."
54
+ },
55
+ {
56
+ "path": "nav.items[].href",
57
+ "availableIn": [
58
+ "layout"
59
+ ],
60
+ "description": "The item's destination path."
61
+ },
62
+ {
63
+ "path": "nav.items[].cta",
64
+ "availableIn": [
65
+ "layout"
66
+ ],
67
+ "description": "True for the call-to-action item (Donate) — style it prominently."
68
+ },
69
+ {
70
+ "path": "nav.items[].children",
71
+ "availableIn": [
72
+ "layout"
73
+ ],
74
+ "description": "Optional sub-items (e.g. a services dropdown). Absent on plain links; when present, render a dropdown/expandable group."
75
+ },
76
+ {
77
+ "path": "socials",
78
+ "availableIn": [
79
+ "layout"
80
+ ],
81
+ "description": "The tenant's configured social links (possibly empty). Render icons via iconSvg — platform-generated markup, safe for the raw filter (the one exception beyond richtext fields)."
82
+ },
83
+ {
84
+ "path": "socials[].platform",
85
+ "availableIn": [
86
+ "layout"
87
+ ],
88
+ "description": "Platform key (facebook, instagram, x, …)."
89
+ },
90
+ {
91
+ "path": "socials[].url",
92
+ "availableIn": [
93
+ "layout"
94
+ ],
95
+ "description": "The profile URL."
96
+ },
97
+ {
98
+ "path": "socials[].iconSvg",
99
+ "availableIn": [
100
+ "layout"
101
+ ],
102
+ "description": "The platform-drawn icon SVG for this network — output with | raw."
103
+ },
104
+ {
105
+ "path": "worship",
106
+ "availableIn": [
107
+ "layout"
108
+ ],
109
+ "description": "The tenant's worship-schedule rail (prayer/service times), or null when not configured — ALWAYS branch on it. Faith-neutral: every label is tenant data. Render it as header chrome (the reference pattern is a utility bar above the nav); omit entirely when null. Rendering it requires declaring supports.worship in the manifest (the validator checks both directions) — the choosers use that declaration to steer worship-enabled tenants."
110
+ },
111
+ {
112
+ "path": "worship.times",
113
+ "availableIn": [
114
+ "layout"
115
+ ],
116
+ "description": "Up to 8 rows of {name, begins, congregation} — name is the prayer/service name; begins and congregation are display strings (times or short text), either may be null."
117
+ },
118
+ {
119
+ "path": "worship.beginsLabel",
120
+ "availableIn": [
121
+ "layout"
122
+ ],
123
+ "description": "Row label for the begins times (tenant-set; defaults to 'Begins')."
124
+ },
125
+ {
126
+ "path": "worship.congregationLabel",
127
+ "availableIn": [
128
+ "layout"
129
+ ],
130
+ "description": "Row label for the congregation times (tenant-set; e.g. \"Jama'at\"; defaults to 'Congregation'). Only meaningful when at least one row carries a congregation value."
131
+ },
132
+ {
133
+ "path": "worship.note",
134
+ "availableIn": [
135
+ "layout"
136
+ ],
137
+ "description": "A short free-text line for the rail (e.g. \"Jum'a 13:15 & 14:15\"), or null."
138
+ },
139
+ {
140
+ "path": "worship.href",
141
+ "availableIn": [
142
+ "layout"
143
+ ],
144
+ "description": "A same-site path the note links to (a service page with full timetables), or null."
145
+ },
146
+ {
147
+ "path": "worship.today",
148
+ "availableIn": [
149
+ "layout"
150
+ ],
151
+ "description": "Today's date, platform-formatted (e.g. \"24 July 2026\")."
152
+ },
153
+ {
154
+ "path": "worship.todayAlt",
155
+ "availableIn": [
156
+ "layout"
157
+ ],
158
+ "description": "Today in the tenant's alternate calendar (Islamic civil calendar, e.g. \"11 Safar 1448\") when they opted in, else null — always branch."
159
+ },
160
+ {
161
+ "path": "events",
162
+ "availableIn": [
163
+ "page:events"
164
+ ],
165
+ "description": "The tenant's published events, soonest first — a CURATED read-only view for display. Transactional steps (RSVP, ticket purchase) stay platform-owned on the detail view."
166
+ },
167
+ {
168
+ "path": "events[].id",
169
+ "availableIn": [
170
+ "page:events"
171
+ ],
172
+ "description": "Stable event id."
173
+ },
174
+ {
175
+ "path": "events[].name",
176
+ "availableIn": [
177
+ "page:events"
178
+ ],
179
+ "description": "The event's name."
180
+ },
181
+ {
182
+ "path": "events[].description",
183
+ "availableIn": [
184
+ "page:events"
185
+ ],
186
+ "description": "Plain-text description, or null — branch on it."
187
+ },
188
+ {
189
+ "path": "events[].registrationMode",
190
+ "availableIn": [
191
+ "page:events"
192
+ ],
193
+ "description": "INFO (no sign-up), RSVP (free sign-up) or TICKETED (paid)."
194
+ },
195
+ {
196
+ "path": "events[].startsAt",
197
+ "availableIn": [
198
+ "page:events"
199
+ ],
200
+ "description": "ISO-8601 start — format with the date filter."
201
+ },
202
+ {
203
+ "path": "events[].endsAt",
204
+ "availableIn": [
205
+ "page:events"
206
+ ],
207
+ "description": "ISO-8601 end, or null."
208
+ },
209
+ {
210
+ "path": "events[].venueName",
211
+ "availableIn": [
212
+ "page:events"
213
+ ],
214
+ "description": "Venue name, or null (online events may have none)."
215
+ },
216
+ {
217
+ "path": "events[].online",
218
+ "availableIn": [
219
+ "page:events"
220
+ ],
221
+ "description": "True for online events."
222
+ },
223
+ {
224
+ "path": "events[].imageUrl",
225
+ "availableIn": [
226
+ "page:events"
227
+ ],
228
+ "description": "Promo image URL, or null — always branch."
229
+ },
230
+ {
231
+ "path": "events[].onSale",
232
+ "availableIn": [
233
+ "page:events"
234
+ ],
235
+ "description": "False before sales open."
236
+ },
237
+ {
238
+ "path": "events[].soldOut",
239
+ "availableIn": [
240
+ "page:events"
241
+ ],
242
+ "description": "True when no capacity remains."
243
+ },
244
+ {
245
+ "path": "events[].priceFrom",
246
+ "availableIn": [
247
+ "page:events"
248
+ ],
249
+ "description": "The cheapest ticket price (number), or null for free/INFO events."
250
+ },
251
+ {
252
+ "path": "events[].currency",
253
+ "availableIn": [
254
+ "page:events"
255
+ ],
256
+ "description": "ISO currency code for priceFrom, or null."
257
+ },
258
+ {
259
+ "path": "events[].detailHref",
260
+ "availableIn": [
261
+ "page:events"
262
+ ],
263
+ "description": "The canonical link to this event's detail view — always use this, never build URLs yourself."
264
+ },
265
+ {
266
+ "path": "course",
267
+ "availableIn": [
268
+ "page:course"
269
+ ],
270
+ "description": "The course this page addresses — a CURATED read-only view for display. The enrolment journey itself is the platform-owned course_enrol island; place it, never rebuild it."
271
+ },
272
+ {
273
+ "path": "course.id",
274
+ "availableIn": [
275
+ "page:course"
276
+ ],
277
+ "description": "Stable course id."
278
+ },
279
+ {
280
+ "path": "course.title",
281
+ "availableIn": [
282
+ "page:course"
283
+ ],
284
+ "description": "The course's title."
285
+ },
286
+ {
287
+ "path": "course.description",
288
+ "availableIn": [
289
+ "page:course"
290
+ ],
291
+ "description": "Plain-text summary, or null — branch on it. Prefer aboutHtml for the body when present."
292
+ },
293
+ {
294
+ "path": "course.aboutHtml",
295
+ "availableIn": [
296
+ "page:course"
297
+ ],
298
+ "description": "The tenant-authored About page as SANITISED HTML (platform-sanitised on save; images resolved) — render with the raw filter inside your prose styles, or null when not written."
299
+ },
300
+ {
301
+ "path": "course.imageUrl",
302
+ "availableIn": [
303
+ "page:course"
304
+ ],
305
+ "description": "The MAIN image (hero) URL, or null — always branch."
306
+ },
307
+ {
308
+ "path": "course.venue",
309
+ "availableIn": [
310
+ "page:course"
311
+ ],
312
+ "description": "Venue display text, or null."
313
+ },
314
+ {
315
+ "path": "course.audience",
316
+ "availableIn": [
317
+ "page:course"
318
+ ],
319
+ "description": "EVERYONE, or CHILD for children-only courses (parents enrol their children)."
320
+ },
321
+ {
322
+ "path": "course.billingMode",
323
+ "availableIn": [
324
+ "page:course"
325
+ ],
326
+ "description": "UPFRONT (one payment), TERM (committed term fee), or ROLLING (monthly with notice)."
327
+ },
328
+ {
329
+ "path": "course.termStart",
330
+ "availableIn": [
331
+ "page:course"
332
+ ],
333
+ "description": "ISO date the term starts, or null — format with the date filter."
334
+ },
335
+ {
336
+ "path": "course.termEnd",
337
+ "availableIn": [
338
+ "page:course"
339
+ ],
340
+ "description": "ISO date the term ends, or null."
341
+ },
342
+ {
343
+ "path": "course.currency",
344
+ "availableIn": [
345
+ "page:course"
346
+ ],
347
+ "description": "ISO currency code for priceFrom."
348
+ },
349
+ {
350
+ "path": "course.priceFrom",
351
+ "availableIn": [
352
+ "page:course"
353
+ ],
354
+ "description": "The cheapest priced option (number), or null when every option is free."
355
+ },
356
+ {
357
+ "path": "course.soldOut",
358
+ "availableIn": [
359
+ "page:course"
360
+ ],
361
+ "description": "True when no option has places left (the island shows the standard note)."
362
+ },
363
+ {
364
+ "path": "course.backHref",
365
+ "availableIn": [
366
+ "page:course"
367
+ ],
368
+ "description": "The canonical link back to the courses listing — always use this."
369
+ }
370
+ ],
371
+ "fixtures": {
372
+ "brand": {
373
+ "name": "Fixture Charity",
374
+ "tagline": "A fixture tagline.",
375
+ "logoUrl": null,
376
+ "logoType": "full"
377
+ },
378
+ "layout": {
379
+ "nav": {
380
+ "items": [
381
+ {
382
+ "label": "Home",
383
+ "href": "/",
384
+ "cta": false
385
+ },
386
+ {
387
+ "label": "About us",
388
+ "href": "/about",
389
+ "cta": false
390
+ },
391
+ {
392
+ "label": "Events",
393
+ "href": "/events",
394
+ "cta": false
395
+ },
396
+ {
397
+ "label": "Services",
398
+ "href": "/services",
399
+ "cta": false,
400
+ "children": [
401
+ {
402
+ "label": "Food bank",
403
+ "href": "/services?service=food-bank"
404
+ },
405
+ {
406
+ "label": "Youth club",
407
+ "href": "/services?service=youth-club"
408
+ }
409
+ ]
410
+ },
411
+ {
412
+ "label": "Donate",
413
+ "href": "/donate",
414
+ "cta": true
415
+ }
416
+ ]
417
+ },
418
+ "socials": [
419
+ {
420
+ "platform": "facebook",
421
+ "url": "https://facebook.com/fixture",
422
+ "iconSvg": "<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/></svg>"
423
+ }
424
+ ],
425
+ "worship": {
426
+ "times": [
427
+ {
428
+ "name": "Fajr",
429
+ "begins": "03:20",
430
+ "congregation": "04:00"
431
+ },
432
+ {
433
+ "name": "Zuhr",
434
+ "begins": "13:11",
435
+ "congregation": "13:30"
436
+ },
437
+ {
438
+ "name": "Asr",
439
+ "begins": "18:30",
440
+ "congregation": "19:00"
441
+ },
442
+ {
443
+ "name": "Maghrib",
444
+ "begins": "21:04",
445
+ "congregation": "21:04"
446
+ },
447
+ {
448
+ "name": "Isha",
449
+ "begins": "22:12",
450
+ "congregation": "22:30"
451
+ }
452
+ ],
453
+ "beginsLabel": "Begins",
454
+ "congregationLabel": "Jama'at",
455
+ "note": "Jum'a 13:15 & 14:15",
456
+ "href": "/services?service=prayer-times",
457
+ "today": "24 July 2026",
458
+ "todayAlt": "11 Safar 1448"
459
+ }
460
+ },
461
+ "pages": {
462
+ "events": {
463
+ "events": [
464
+ {
465
+ "id": "11111111-1111-1111-1111-111111111111",
466
+ "name": "Summer Gala",
467
+ "description": "An evening of music and giving.",
468
+ "registrationMode": "TICKETED",
469
+ "startsAt": "2030-08-15T18:00:00Z",
470
+ "endsAt": null,
471
+ "venueName": "Town Hall",
472
+ "online": false,
473
+ "imageUrl": null,
474
+ "onSale": true,
475
+ "soldOut": false,
476
+ "priceFrom": 10.0,
477
+ "currency": "GBP",
478
+ "detailHref": "/events?event=11111111-1111-1111-1111-111111111111"
479
+ },
480
+ {
481
+ "id": "22222222-2222-2222-2222-222222222222",
482
+ "name": "Coffee Morning",
483
+ "description": null,
484
+ "registrationMode": "RSVP",
485
+ "startsAt": "2030-08-01T10:00:00Z",
486
+ "endsAt": "2030-08-01T12:00:00Z",
487
+ "venueName": null,
488
+ "online": true,
489
+ "imageUrl": null,
490
+ "onSale": true,
491
+ "soldOut": true,
492
+ "priceFrom": null,
493
+ "currency": null,
494
+ "detailHref": "/events?event=22222222-2222-2222-2222-222222222222"
495
+ },
496
+ {
497
+ "id": "33333333-3333-3333-3333-333333333333",
498
+ "name": "Open Day",
499
+ "description": "Drop in and meet the team.",
500
+ "registrationMode": "INFO",
501
+ "startsAt": "2030-09-01T09:00:00Z",
502
+ "endsAt": null,
503
+ "venueName": "Community Centre",
504
+ "online": false,
505
+ "imageUrl": null,
506
+ "onSale": true,
507
+ "soldOut": false,
508
+ "priceFrom": null,
509
+ "currency": null,
510
+ "detailHref": "/events?event=33333333-3333-3333-3333-333333333333"
511
+ }
512
+ ]
513
+ },
514
+ "course": {
515
+ "course": {
516
+ "id": "33333333-3333-3333-3333-333333333333",
517
+ "title": "Junior Athletics",
518
+ "description": "Weekly athletics for children aged 7–12.",
519
+ "aboutHtml": "<h2>What we do</h2><p>Track and field fundamentals in a friendly, coached setting.</p><ul><li>Warm-ups and drills</li><li>Sprints, jumps and throws</li></ul>",
520
+ "imageUrl": null,
521
+ "venue": "King's Park track",
522
+ "audience": "CHILD",
523
+ "billingMode": "ROLLING",
524
+ "termStart": null,
525
+ "termEnd": null,
526
+ "currency": "GBP",
527
+ "priceFrom": 15.0,
528
+ "soldOut": false,
529
+ "backHref": "/courses"
530
+ }
531
+ }
532
+ }
533
+ }
534
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "format": "port60-liquid@1",
3
+ "description": "The Port60 template dialect — a strict, whitelisted subset of Liquid. Anything not listed here is rejected at parse time by the engine and at publish time by the conformance validator.",
4
+ "outputEscape": "escape",
5
+ "rawFilter": "raw",
6
+ "tags": [
7
+ "if", "elsif", "else", "endif",
8
+ "unless", "endunless",
9
+ "case", "when", "endcase",
10
+ "for", "endfor", "break", "continue",
11
+ "assign", "capture", "endcapture",
12
+ "comment", "endcomment",
13
+ "echo", "raw", "endraw", "liquid"
14
+ ],
15
+ "customTags": [
16
+ {
17
+ "name": "island",
18
+ "usage": "{% island 'donation_widget' %}",
19
+ "description": "Places a platform functional island (a live, hydrated component) at this position. Templates place islands, never implement them — the island's markup, behaviour and data are platform-owned and upgraded platform-side. The island name must be declared in the manifest's supports.islands."
20
+ },
21
+ {
22
+ "name": "content",
23
+ "usage": "{% content %}",
24
+ "description": "LAYOUT ONLY: the slot where the page's rendered sections are injected. A layout must contain exactly one. Section renderers must not use it."
25
+ }
26
+ ],
27
+ "filters": [
28
+ "abs", "append", "at_least", "at_most", "capitalize", "ceil", "compact", "concat",
29
+ "date", "default", "divided_by", "downcase", "escape", "escape_once", "first", "floor",
30
+ "join", "last", "lstrip", "map", "minus", "modulo", "newline_to_br", "plus", "prepend",
31
+ "raw", "remove", "remove_first", "replace", "replace_first", "reverse", "round", "rstrip",
32
+ "size", "slice", "sort", "sort_natural", "split", "strip", "strip_html", "strip_newlines",
33
+ "times", "truncate", "truncatewords", "uniq", "upcase", "url_decode", "url_encode", "where"
34
+ ],
35
+ "excluded": {
36
+ "tags": ["include", "render", "layout", "tablerow", "cycle", "increment", "decrement"],
37
+ "reason": "File inclusion (no file system exists inside an artifact yet — a snippets mechanism arrives with the Layout slot), table markup generation, and stateful counters are outside the v1 dialect."
38
+ }
39
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "description": "The curated font catalogue behind `font` settings knobs. The tenant picks a FAMILY; the template's typographic system (weights, sizes, tracking) is author-owned — the host requests the template's declared weights for the chosen family, clamped to the family's real weights below (nearest wins). Curated, not the raw Google catalogue: every family here is open-licence (OFL/Apache), quality-checked, and tagged so non-technical admins choose by vibe. Served from Bunny Fonts (a privacy-first, EU-hosted drop-in mirror of the Google Fonts catalogue — same families, same css2 API). Additive-only within a contract major; never remove a family (published sites reference them).",
3
+ "provider": {
4
+ "cssBase": "https://fonts.bunny.net/css2",
5
+ "preconnect": "https://fonts.bunny.net"
6
+ },
7
+ "families": [
8
+ { "name": "Lora", "category": "serif", "tags": ["warm", "friendly"], "weights": [400, 500, 600, 700], "stack": "serif" },
9
+ { "name": "Newsreader", "category": "serif", "tags": ["editorial", "elegant"], "weights": [400, 500, 600, 700, 800], "stack": "serif" },
10
+ { "name": "Merriweather", "category": "serif", "tags": ["sturdy", "readable"], "weights": [300, 400, 700, 900], "stack": "serif" },
11
+ { "name": "Playfair Display", "category": "serif", "tags": ["elegant", "editorial"], "weights": [400, 500, 600, 700, 800, 900], "stack": "serif" },
12
+ { "name": "EB Garamond", "category": "serif", "tags": ["classic", "elegant"], "weights": [400, 500, 600, 700, 800], "stack": "serif" },
13
+ { "name": "Cormorant Garamond", "category": "serif", "tags": ["refined", "display"], "weights": [300, 400, 500, 600, 700], "stack": "serif" },
14
+ { "name": "Crimson Pro", "category": "serif", "tags": ["bookish", "readable"], "weights": [300, 400, 500, 600, 700, 800], "stack": "serif" },
15
+ { "name": "Libre Baskerville", "category": "serif", "tags": ["classic", "readable"], "weights": [400, 700], "stack": "serif" },
16
+ { "name": "Source Serif 4", "category": "serif", "tags": ["modern", "readable"], "weights": [300, 400, 500, 600, 700], "stack": "serif" },
17
+ { "name": "Bitter", "category": "serif", "tags": ["slab", "sturdy"], "weights": [300, 400, 500, 600, 700, 800], "stack": "serif" },
18
+ { "name": "Fraunces", "category": "serif", "tags": ["characterful", "display"], "weights": [300, 400, 500, 600, 700, 800, 900], "stack": "serif" },
19
+ { "name": "IBM Plex Serif", "category": "serif", "tags": ["modern", "neutral"], "weights": [300, 400, 500, 600, 700], "stack": "serif" },
20
+ { "name": "Zilla Slab", "category": "serif", "tags": ["slab", "friendly"], "weights": [300, 400, 500, 600, 700], "stack": "serif" },
21
+ { "name": "Roboto Slab", "category": "serif", "tags": ["slab", "neutral"], "weights": [300, 400, 500, 600, 700, 800], "stack": "serif" },
22
+ { "name": "Arvo", "category": "serif", "tags": ["slab", "sturdy"], "weights": [400, 700], "stack": "serif" },
23
+
24
+ { "name": "Inter", "category": "sans", "tags": ["neutral", "modern"], "weights": [300, 400, 500, 600, 700, 800, 900], "stack": "sans-serif" },
25
+ { "name": "Montserrat", "category": "sans", "tags": ["geometric", "confident"], "weights": [300, 400, 500, 600, 700, 800, 900], "stack": "sans-serif" },
26
+ { "name": "Poppins", "category": "sans", "tags": ["geometric", "friendly"], "weights": [300, 400, 500, 600, 700, 800, 900], "stack": "sans-serif" },
27
+ { "name": "Open Sans", "category": "sans", "tags": ["neutral", "readable"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
28
+ { "name": "Lato", "category": "sans", "tags": ["warm", "readable"], "weights": [300, 400, 700, 900], "stack": "sans-serif" },
29
+ { "name": "Nunito", "category": "sans", "tags": ["rounded", "friendly"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
30
+ { "name": "Work Sans", "category": "sans", "tags": ["clean", "modern"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
31
+ { "name": "Manrope", "category": "sans", "tags": ["modern", "confident"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
32
+ { "name": "DM Sans", "category": "sans", "tags": ["clean", "geometric"], "weights": [400, 500, 700], "stack": "sans-serif" },
33
+ { "name": "Raleway", "category": "sans", "tags": ["elegant", "light"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
34
+ { "name": "Rubik", "category": "sans", "tags": ["rounded", "modern"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
35
+ { "name": "Karla", "category": "sans", "tags": ["grotesque", "warm"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
36
+ { "name": "Jost", "category": "sans", "tags": ["geometric", "elegant"], "weights": [300, 400, 500, 600, 700], "stack": "sans-serif" },
37
+ { "name": "Outfit", "category": "sans", "tags": ["geometric", "modern"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
38
+ { "name": "Sora", "category": "sans", "tags": ["technical", "modern"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
39
+ { "name": "Space Grotesk", "category": "sans", "tags": ["characterful", "modern"], "weights": [300, 400, 500, 600, 700], "stack": "sans-serif" },
40
+ { "name": "Figtree", "category": "sans", "tags": ["clean", "friendly"], "weights": [300, 400, 500, 600, 700, 800, 900], "stack": "sans-serif" },
41
+ { "name": "Plus Jakarta Sans", "category": "sans", "tags": ["modern", "confident"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
42
+ { "name": "Barlow", "category": "sans", "tags": ["neutral", "condensed-friendly"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
43
+ { "name": "IBM Plex Sans", "category": "sans", "tags": ["modern", "neutral"], "weights": [300, 400, 500, 600, 700], "stack": "sans-serif" },
44
+ { "name": "Albert Sans", "category": "sans", "tags": ["geometric", "clean"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
45
+
46
+ { "name": "Quicksand", "category": "rounded", "tags": ["friendly", "soft"], "weights": [300, 400, 500, 600, 700], "stack": "sans-serif" },
47
+ { "name": "Comfortaa", "category": "rounded", "tags": ["soft", "playful"], "weights": [300, 400, 500, 600, 700], "stack": "sans-serif" },
48
+ { "name": "Baloo 2", "category": "rounded", "tags": ["playful", "bold"], "weights": [400, 500, 600, 700, 800], "stack": "sans-serif" },
49
+ { "name": "Fredoka", "category": "rounded", "tags": ["playful", "friendly"], "weights": [300, 400, 500, 600, 700], "stack": "sans-serif" },
50
+
51
+ { "name": "Oswald", "category": "display", "tags": ["condensed", "impactful"], "weights": [300, 400, 500, 600, 700], "stack": "sans-serif" },
52
+ { "name": "Bebas Neue", "category": "display", "tags": ["condensed", "loud"], "weights": [400], "stack": "sans-serif" },
53
+ { "name": "Josefin Sans", "category": "display", "tags": ["vintage", "elegant"], "weights": [300, 400, 500, 600, 700], "stack": "sans-serif" },
54
+ { "name": "Archivo", "category": "display", "tags": ["grotesque", "impactful"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
55
+ { "name": "Abril Fatface", "category": "display", "tags": ["fat", "editorial"], "weights": [400], "stack": "serif" },
56
+ { "name": "Alfa Slab One", "category": "display", "tags": ["slab", "loud"], "weights": [400], "stack": "serif" },
57
+
58
+ { "name": "Cairo", "category": "arabic", "tags": ["arabic", "sans", "modern"], "weights": [300, 400, 500, 600, 700, 800], "stack": "sans-serif" },
59
+ { "name": "Tajawal", "category": "arabic", "tags": ["arabic", "sans", "clean"], "weights": [300, 400, 500, 700, 800], "stack": "sans-serif" },
60
+ { "name": "Almarai", "category": "arabic", "tags": ["arabic", "sans", "readable"], "weights": [300, 400, 700, 800], "stack": "sans-serif" },
61
+ { "name": "IBM Plex Sans Arabic", "category": "arabic", "tags": ["arabic", "sans", "modern"], "weights": [300, 400, 500, 600, 700], "stack": "sans-serif" },
62
+ { "name": "Amiri", "category": "arabic", "tags": ["arabic", "serif", "classic"], "weights": [400, 700], "stack": "serif" },
63
+ { "name": "Noto Kufi Arabic", "category": "arabic", "tags": ["arabic", "sans", "geometric"], "weights": [300, 400, 500, 600, 700], "stack": "sans-serif" },
64
+ { "name": "Noto Naskh Arabic", "category": "arabic", "tags": ["arabic", "serif", "readable"], "weights": [400, 500, 600, 700], "stack": "serif" },
65
+ { "name": "Scheherazade New", "category": "arabic", "tags": ["arabic", "serif", "traditional"], "weights": [400, 500, 600, 700], "stack": "serif" }
66
+ ]
67
+ }