@arraypress/covers 1.3.1 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +1 -1
  3. package/package.json +3 -2
  4. package/recipes/flyers/construction.json +36 -0
  5. package/recipes/flyers/fluted-poster.json +95 -0
  6. package/recipes/flyers/grid-poster.json +91 -0
  7. package/recipes/flyers/logofolio.json +60 -0
  8. package/recipes/flyers/pixel-mosaic.json +58 -0
  9. package/recipes/flyers/series-card.json +81 -0
  10. package/schema/recipe.schema.json +768 -4
  11. package/src/index.d.ts +9 -7
  12. package/src/json-recipe.js +5 -0
  13. package/src/layer-options.js +7 -3
  14. package/src/layers/construction.d.ts +34 -0
  15. package/src/layers/construction.js +137 -0
  16. package/src/layers/guides.d.ts +38 -0
  17. package/src/layers/guides.js +212 -0
  18. package/src/layers/index.d.ts +12 -0
  19. package/src/layers/index.js +6 -0
  20. package/src/layers/isolines.d.ts +21 -0
  21. package/src/layers/isolines.js +271 -0
  22. package/src/layers/mark-draw.d.ts +21 -0
  23. package/src/layers/mark-draw.js +93 -0
  24. package/src/layers/mark-geometry.d.ts +15 -0
  25. package/src/layers/mark-geometry.js +46 -0
  26. package/src/layers/marks.d.ts +25 -0
  27. package/src/layers/marks.js +102 -0
  28. package/src/layers/mosaic.d.ts +32 -0
  29. package/src/layers/mosaic.js +155 -0
  30. package/src/layers/outline.d.ts +26 -0
  31. package/src/layers/outline.js +105 -0
  32. package/src/layers.js +7 -6
  33. package/src/type/fallback.js +1 -1
  34. package/src/type/spans.d.ts +5 -1
  35. package/src/type/spans.js +26 -3
  36. package/src/type/symbols.d.ts +1 -1
  37. package/src/type/symbols.js +20 -2
  38. package/src/type/typeset.js +6 -2
  39. package/src/version.js +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,45 @@ All notable changes to `@arraypress/covers` are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.5.1] — 2026-11-06
9
+
10
+ ### Changed
11
+
12
+ - **`fluted-poster`** keeps the name readable: with `subject: 'name'` (or no mark) and no `preset`, the glass is gentler (12 convex ribs at refraction 0.15, little defocus, chroma 0.2), so the word steps at the rib edges but never doubles. A `preset` given is used as it is; the mark keeps the `fluted` preset. `preset` now defaults to `''` (the subject's default).
13
+
14
+ ## [1.5.0] — 2026-11-06
15
+
16
+ Three more posters on the 400-wide grid, and the layers they stand on. Every published recipe renders as before (157 SVGs, flyers at 4:5 and as release covers, the release styles and the built-ins, byte-identical to 1.4.0).
17
+
18
+ ### Added
19
+
20
+ - **Three posters**, drawn on a 400-wide grid as tall as the canvas, so they set at 4:5, 1:1, 9:16 and A-series portrait alike: `construction` (a construction sheet: the mark large over its own construction, read from its paths, a dashed grid, its box, a centre cross, the inscribed and circumscribed circles, the circles its curves are drawn on and the lines its straight edges lie on, in one thin accent; meta in the corners; dark or light by `ground` and `ink`), `fluted-poster` (the mark, or the name set huge, in white on black behind fluted glass, a soft light in one accent behind it, and vector Swiss type over it: the year stacked, the series, a two-line title with a glitched `||` of bars, credits and an edition line; `preset` picks the glass) and `series-card` (a numbered card for a run of posts: a flat soft colour, a two-line title, a large number and the mark solid or as contour lines).
21
+ - **`construction` layer**: a mark over its construction guides (`markGuides`: circles fitted to its arcs and Béziers, lines along its straight edges, added up where several lie on one); `grid`, `size`, `circles`, `lines`, `color`, `gridColor`, `gridOpacity`, `markColor`, `over` (the guides shown faintly over the mark), `stroke`.
22
+ - **`outline` layer**: a mark fitted by its real bounds and drawn as contour lines, its outline and `lines − 1` more `gap` apart (isolines of its signed distance field, from `@logomarks/core`'s raster fill and distance transform, measured exactly to its edges where the lines run, so parts merge as contours do and cusps stay sharp), out or `in`; `lines: 0` draws it solid.
23
+ - The image effects of `@logomarks/image-effects` 0.2–0.3 on any layer, and in the schema: `fluted` (reeded glass; presets `fluted`, `reeded-deep`, `reeded-prism`, `reeded-soft`) and `print` (letterpress, riso and stamp finishes).
24
+
25
+ ### Changed
26
+
27
+ - Depends on `@logomarks/image-effects` `^0.3.2`.
28
+
29
+ ## [1.4.0] — 2026-11-06
30
+
31
+ Three flyers that set on any canvas, and the layers they stand on. Every published recipe renders as before (185 SVGs, flyers at 4:5 and as release covers plus the built-ins, byte-identical to 1.3.1).
32
+
33
+ ### Added
34
+
35
+ - **Three flyers**, drawn on a 400-wide grid as tall as the canvas, so they set at 4:5, 1:1, 9:16 and A-series portrait alike: `grid-poster` (a Swiss grid poster: a photo washed in one colour or a grainy field, a hairline grid of five columns whose rows follow the aspect, meta in its cells, the mark on a tile in one cell, a two-weight headline fitted to four columns), `logofolio` (a collection sheet: a title and meta over a grid of up to 70 marks, optically sized the same) and `pixel-mosaic` (the mark rastered into big neon cells over a greyscale photo, a few left out by seed, the name huge across the foot).
36
+ - **`mosaic` layer**: a mark rastered into square cells drawn apart, following its shape (`@logomarks/core`'s pixelate); `cells`, `gap`, `drop` (a seeded share left out), `accent` (an asterisk or dot in one empty cell).
37
+ - **`marks` layer**: a set of marks on an even grid, each optically sized by its real bounds and ink and held to its cell; columns and rows from the count (3 × 3 to 10 × 7).
38
+ - **Text lines as spans**: a `content` array may hold span objects, one a line (`[{ content: 'Signal', bold: true }, 'over the']`); `bold: true` on any span sets it at the layer's `boldWeight` (700).
39
+ - **JSON**: `round`, `floor` and `ceil` operators (a grid's row count from the canvas).
40
+ - `pattern` takes `divisions: [cols, rows]` for a design grid.
41
+ - `≠` in the built-in symbols, for fonts whose Latin subset lacks it.
42
+
43
+ ### Changed
44
+
45
+ - Depends on `@logomarks/core` `^0.1.5` (pixelate without a canvas).
46
+
8
47
  ## [1.3.1] — 2026-11-06
9
48
 
10
49
  ### Changed
package/README.md CHANGED
@@ -36,7 +36,7 @@ await writeFile('public/covers/introducing-covers.webp', webp);
36
36
  - [API](https://github.com/arraypress/covers/blob/main/docs/api.md): every export and cover option
37
37
  - [Layers](https://github.com/arraypress/covers/blob/main/docs/layers.md): mesh, grain, text, diagrams, riso inks, dithers, poster type and more
38
38
  - [Effects](https://github.com/arraypress/covers/blob/main/docs/effects.md): filters and pixel effects on any layer, masks, paints
39
- - [Recipes](https://github.com/arraypress/covers/blob/main/docs/recipes.md): seventeen built-ins, 84 flyers and release covers, your own
39
+ - [Recipes](https://github.com/arraypress/covers/blob/main/docs/recipes.md): seventeen built-ins, 90 flyers and release covers, your own
40
40
  - [JSON recipes](https://github.com/arraypress/covers/blob/main/docs/json-recipes.md): a cover style as data, with no JavaScript
41
41
  - [Palettes](https://github.com/arraypress/covers/blob/main/docs/palettes.md): presets, one accent, a theme's tokens
42
42
  - [Formats, print and brand kits](https://github.com/arraypress/covers/blob/main/docs/formats.md): social sizes with safe zones, print with bleed, `kit`, `lint`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arraypress/covers",
3
- "version": "1.3.1",
3
+ "version": "1.5.1",
4
4
  "description": "Covers, flyers, posters, release art, social formats and print stationery from one seed and a stack of layers: mesh gradients, grain, riso inks and halftones, knockout and justified type, torn paper, barcodes, safe zones and bleed, as SVG or WebP/PNG/AVIF/JPEG. Recipes are JSON. Runs in Node, browsers and Cloudflare Workers.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -85,7 +85,8 @@
85
85
  },
86
86
  "dependencies": {
87
87
  "@arraypress/color-utils": "^1.3.0",
88
- "@logomarks/image-effects": "^0.1.1",
88
+ "@logomarks/core": "^0.1.5",
89
+ "@logomarks/image-effects": "^0.3.2",
89
90
  "harfbuzzjs": "^1.6.3"
90
91
  },
91
92
  "peerDependencies": {
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "../../schema/recipe.schema.json",
3
+ "name": "construction",
4
+ "description": "Construction sheet: the mark large and centred over its own construction, read from its paths: a light dashed square grid behind (cells an eighth of the mark by default, centred on it), its bounding box, a centre cross, the circles inscribed in its box and round its outline, the circles its curves are drawn on (each centre marked) and the lines its straight edges lie on, run across the sheet, all in one thin accent (blue by default) and showing faintly over the mark. Small meta in the corners: a label and the name across the top, the grid's size and a ® (or any caption) along the foot. A dark ground by default; set ground and ink for a light one. Drawn on a 400-wide grid as tall as the canvas, so it sets at 4:5, 1:1, 9:16 and A-series portrait alike.",
5
+ "requires": ["mark"],
6
+ "params": {
7
+ "name": "Kestrel",
8
+ "meta": "Construction · v1.0",
9
+ "gridLabel": "",
10
+ "caption": "®",
11
+ "grid": 8,
12
+ "size": 0.58,
13
+ "ground": "#0b0b0c",
14
+ "ink": "#f2f1ec",
15
+ "accent": "#3b82f6"
16
+ },
17
+ "vars": {
18
+ "h": { "mul": [400, { "div": ["@height", "@width"] }] }
19
+ },
20
+ "layers": [
21
+ { "id": "ground", "type": "solid", "color": "{ground}" },
22
+ {
23
+ "id": "sheet",
24
+ "type": "group",
25
+ "viewBox": [0, 0, 400, "$h"],
26
+ "fit": "contain",
27
+ "layers": [
28
+ { "id": "construction", "type": "construction", "markup": "{mark}", "x": 16, "y": 40, "width": 368, "height": { "sub": ["$h", 80] }, "size": "{size}", "grid": "{grid}", "color": "{accent}", "markColor": "{ink}", "stroke": 0.55 },
29
+ { "id": "meta", "type": "text", "content": "{meta}", "when": "meta", "font": "Inter Tight", "weight": 500, "size": 7, "letterSpacing": 0, "x": 16, "y": 24, "maxWidth": 200, "maxLines": 1, "color": "{ink}" },
30
+ { "id": "name", "type": "text", "content": "{name}", "when": "name", "font": "Inter Tight", "weight": 600, "size": 7, "letterSpacing": 0, "x": 384, "y": 24, "align": "right", "maxWidth": 160, "maxLines": 1, "color": "{ink}" },
31
+ { "id": "gridLabel", "type": "text", "content": { "if": "gridLabel", "then": "{gridLabel}", "else": { "join": [["Grid ", "{grid}", " × ", "{grid}"], ""] } }, "when": { "any": ["gridLabel", "grid"] }, "font": "Inter Tight", "weight": 500, "size": 7, "letterSpacing": 0, "x": 16, "y": { "sub": ["$h", 18] }, "maxWidth": 200, "maxLines": 1, "color": "{ink}", "opacity": 0.6 },
32
+ { "id": "caption", "type": "text", "content": "{caption}", "when": "caption", "font": "Inter Tight", "weight": 500, "size": 9, "letterSpacing": 0, "x": 384, "y": { "sub": ["$h", 17] }, "align": "right", "maxWidth": 160, "maxLines": 1, "color": "{ink}" }
33
+ ]
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,95 @@
1
+ {
2
+ "$schema": "../../schema/recipe.schema.json",
3
+ "name": "fluted-poster",
4
+ "description": "Fluted glass poster: the mark (or the name, set huge) in white on black, seen through reeded glass (@logomarks/image-effects' fluted effect: by default its fluted preset for a mark, and gentler glass for the name, 12 wide convex ribs at light refraction (0.15) and little defocus, so the word steps at the rib edges but never doubles; a preset given, fluted, reeded-deep, reeded-prism or reeded-soft, is used as it is), with a soft light in one accent colour behind it (none with accent \"\"). Over the glass, small Swiss type stays vector: the year stacked large top-left, the series top-right, a two-line title bottom-left with a glitched || ornament of plain bars right of it in the accent, credits and an edition line along the foot. The glass is rastered at the output size and embedded; the type is outlines. Drawn on a 400-wide grid as tall as the canvas, so it sets at 4:5, 1:1, 9:16 and A-series portrait alike; the ornament's glitch follows the seed.",
5
+ "params": {
6
+ "name": "Kestrel",
7
+ "subject": "mark",
8
+ "preset": "",
9
+ "title": ["Through", "the glass"],
10
+ "year": "2026",
11
+ "series": "",
12
+ "credits": "",
13
+ "edition": "Posters / Series 03",
14
+ "accent": "#ff4d1a",
15
+ "ground": "#000000",
16
+ "ink": "#ffffff"
17
+ },
18
+ "vars": {
19
+ "h": { "mul": [400, { "div": ["@height", "@width"] }] },
20
+ "bar": { "if": "accent", "then": "{accent}", "else": "{ink}" },
21
+ "tall": { "min": [2, { "max": [1, { "div": ["$h", 380] }] }] },
22
+ "s1": { "range": [-3, 3] },
23
+ "s2": { "range": [-3, 3] },
24
+ "s3": { "range": [-3, 3] },
25
+ "cut": { "range": [0.3, 0.6] }
26
+ },
27
+ "layers": [
28
+ { "id": "ground", "type": "solid", "color": "{ground}" },
29
+ {
30
+ "id": "glass",
31
+ "type": "group",
32
+ "viewBox": [0, 0, 400, "$h"],
33
+ "fit": "contain",
34
+ "effects": [{ "kind": "fluted", "preset": { "if": "preset", "then": "{preset}", "else": "fluted" }, "profile": { "if": { "all": [{ "any": [{ "subject": "name" }, { "not": "mark" }] }, { "not": "preset" }] }, "then": "convex" }, "ribs": { "if": { "all": [{ "any": [{ "subject": "name" }, { "not": "mark" }] }, { "not": "preset" }] }, "then": 12 }, "refraction": { "if": { "all": [{ "any": [{ "subject": "name" }, { "not": "mark" }] }, { "not": "preset" }] }, "then": 0.15 }, "stretch": { "if": { "all": [{ "any": [{ "subject": "name" }, { "not": "mark" }] }, { "not": "preset" }] }, "then": "0.4vmin" }, "chroma": { "if": { "all": [{ "any": [{ "subject": "name" }, { "not": "mark" }] }, { "not": "preset" }] }, "then": 0.2 } }],
35
+ "layers": [
36
+ { "id": "light", "type": "radial", "when": "accent", "x": 0.5, "y": 0.47, "colors": ["{accent}", "{ground}"], "radius": "52vmin", "opacity": 0.75 },
37
+ { "id": "mark", "type": "outline", "when": { "all": ["mark", { "not": { "subject": "name" } }] }, "markup": "{mark}", "lines": 0, "x": 70, "y": { "sub": [{ "mul": ["$h", 0.47] }, 130] }, "width": 260, "height": 260, "color": "{ink}" },
38
+ {
39
+ "id": "word",
40
+ "type": "group",
41
+ "when": { "any": [{ "subject": "name" }, { "not": "mark" }] },
42
+ "transform": { "scale": [1, "$tall"], "origin": [200, { "mul": ["$h", 0.47] }] },
43
+ "layers": [
44
+ { "id": "name", "type": "text", "content": "{name}", "transform": "uppercase", "font": "Inter Tight", "weight": 800, "size": 120, "lineHeight": 0.9, "letterSpacing": -0.05, "fit": "width", "maxWidth": 360, "maxHeight": { "div": [{ "mul": ["$h", 0.42] }, "$tall"] }, "anchor": "center", "margin": 0, "offset": { "x": 0, "y": { "mul": ["$h", -0.03] } }, "color": "{ink}" }
45
+ ]
46
+ }
47
+ ]
48
+ },
49
+ {
50
+ "id": "type",
51
+ "type": "group",
52
+ "viewBox": [0, 0, 400, "$h"],
53
+ "fit": "contain",
54
+ "layers": [
55
+ { "id": "year", "type": "text", "when": "year", "content": [{ "slice": ["{year}", 0, 2] }, { "slice": ["{year}", 2] }], "font": "Inter Tight", "weight": 700, "size": 26, "lineHeight": 0.86, "letterSpacing": -0.04, "x": 15, "y": 36, "color": "{ink}" },
56
+ { "id": "series", "type": "text", "content": { "if": "series", "then": "{series}", "else": "{name}\nGlass series" }, "font": "Inter Tight", "weight": 500, "size": 7, "lineHeight": 1.25, "letterSpacing": 0, "x": 384, "y": 24, "align": "right", "maxWidth": 140, "maxLines": 3, "color": "{ink}" },
57
+ {
58
+ "id": "title",
59
+ "type": "text",
60
+ "content": "{title}",
61
+ "when": "title",
62
+ "transform": "uppercase",
63
+ "font": "Inter Tight",
64
+ "weight": 700,
65
+ "size": 24,
66
+ "lineHeight": 0.92,
67
+ "letterSpacing": -0.035,
68
+ "fit": "shrink",
69
+ "maxWidth": 300,
70
+ "maxLines": 2,
71
+ "anchor": "bottom-left",
72
+ "margin": 0,
73
+ "offset": { "x": 15, "y": -46 },
74
+ "color": "{ink}"
75
+ },
76
+ {
77
+ "id": "ornament",
78
+ "type": "shape",
79
+ "fill": "$bar",
80
+ "items": [
81
+ { "kind": "rect", "x": 362, "y": { "sub": ["$h", 90] }, "width": 5, "height": { "mul": [44, "$cut"] } },
82
+ { "kind": "rect", "x": { "add": [362, "$s1"] }, "y": { "add": [{ "sub": ["$h", 90] }, { "mul": [44, "$cut"] }, 1.5] }, "width": 5, "height": 5 },
83
+ { "kind": "rect", "x": 362, "y": { "add": [{ "sub": ["$h", 90] }, { "mul": [44, "$cut"] }, 8] }, "width": 5, "height": { "sub": [36, { "mul": [44, "$cut"] }] } },
84
+ { "kind": "rect", "x": 375, "y": { "sub": ["$h", 90] }, "width": 5, "height": { "sub": [36, { "mul": [44, "$cut"] }] } },
85
+ { "kind": "rect", "x": { "add": [375, "$s2"] }, "y": { "add": [{ "sub": ["$h", 90] }, { "sub": [36, { "mul": [44, "$cut"] }] }, 1.5] }, "width": 5, "height": 5 },
86
+ { "kind": "rect", "x": { "add": [375, { "mul": ["$s3", 0.5] }] }, "y": { "add": [{ "sub": ["$h", 90] }, { "sub": [36, { "mul": [44, "$cut"] }] }, 8] }, "width": 5, "height": { "mul": [44, "$cut"] } }
87
+ ]
88
+ },
89
+ { "id": "credits", "type": "text", "content": { "if": "credits", "then": "{credits}", "else": "Design — {name}\nSet in Inter Tight" }, "font": "Inter Tight", "weight": 500, "size": 6.5, "lineHeight": 1.3, "letterSpacing": 0, "x": 16, "y": { "sub": ["$h", 27] }, "maxWidth": 180, "maxLines": 2, "color": "{ink}", "opacity": 0.72 },
90
+ { "id": "edition", "type": "text", "content": "{edition}", "when": "edition", "font": "Inter Tight", "weight": 500, "size": 6.5, "letterSpacing": 0, "x": 384, "y": { "sub": ["$h", 18.5] }, "align": "right", "maxWidth": 180, "maxLines": 1, "color": "{ink}", "opacity": 0.72 }
91
+ ]
92
+ },
93
+ { "id": "print", "type": "grain", "amount": 0.035, "size": 1.5 }
94
+ ]
95
+ }
@@ -0,0 +1,91 @@
1
+ {
2
+ "$schema": "../../schema/recipe.schema.json",
3
+ "name": "grid-poster",
4
+ "description": "Swiss grid poster: a moody full-bleed photo washed in one strong colour (or, with no photo, a grainy gradient field in it), a hairline grid of five columns over the whole sheet, tiny meta hung in its cells (series, handle and year across the top; a label, a code and coordinates on the middle row; the credit and a save prompt along the foot), the mark small on a white tile in one cell, and a big uppercase headline whose lines alternate bold and light, fitted to the four columns right of the first column line, with a small sub-line under it. The grid is drawn on a 400-wide design grid as tall as the canvas, so its rows follow the aspect: eight at 4:5, six at 1:1, eleven at 9:16. Headline lines are strings or { content, bold }.",
5
+ "params": {
6
+ "name": "Kestrel",
7
+ "headline": [
8
+ { "content": "Signal", "bold": true },
9
+ { "content": "over the", "bold": false },
10
+ { "content": "noise", "bold": true }
11
+ ],
12
+ "sub": "Visibility ≠ Clarity",
13
+ "series": "",
14
+ "handle": "",
15
+ "year": "©2026",
16
+ "label": "Edition",
17
+ "code": "Nº 014",
18
+ "coords": "51.5072° N\n0.1276° W",
19
+ "credit": "",
20
+ "save": "Save this for later",
21
+ "wash": "#ff5a1f",
22
+ "ink": "#ffffff",
23
+ "tile": "#ffffff",
24
+ "markColor": ""
25
+ },
26
+ "vars": {
27
+ "h": { "mul": [400, { "div": ["@height", "@width"] }] },
28
+ "rows": { "max": [4, { "round": { "div": ["$h", 62.5] } }] },
29
+ "rowH": { "div": ["$h", "$rows"] },
30
+ "mid": { "floor": { "div": [{ "sub": ["$rows", 1] }, 2] } },
31
+ "midTop": { "mul": ["$mid", "$rowH"] },
32
+ "footTop": { "sub": ["$h", "$rowH"] },
33
+ "deep": { "readable": ["{ink}", { "tone": ["{wash}", { "l": [0.5, 0.58] }] }, { "tone": ["{wash}", { "l": [0.12, 0.2] }] }] },
34
+ "glow": { "tone": ["{wash}", { "l": [0.72, 0.8] }] },
35
+ "markInk": { "if": "markColor", "then": "{markColor}", "else": { "tone": ["{wash}", { "l": [0.3, 0.58] }] } }
36
+ },
37
+ "layers": [
38
+ { "id": "photo", "type": "image", "when": "photo", "src": "{photo}", "fit": "cover", "duotone": ["$deep", "{wash}"] },
39
+ { "id": "field", "type": "radial", "when": { "not": "photo" }, "colors": ["$glow", "{wash}", "$deep"], "radius": "110vmax" },
40
+ { "id": "fieldGrain", "type": "grain", "when": { "not": "photo" }, "amount": 0.16, "vary": "edges" },
41
+ {
42
+ "id": "poster",
43
+ "type": "group",
44
+ "viewBox": [0, 0, 400, "$h"],
45
+ "fit": "contain",
46
+ "layers": [
47
+ { "id": "grid", "type": "pattern", "kind": "grid", "divisions": [5, "$rows"], "color": "{ink}", "stroke": 0.5, "opacity": 0.12 },
48
+ { "id": "tile", "type": "shape", "kind": "rect", "x": 6, "y": { "add": ["$rowH", 6] }, "width": 68, "height": { "sub": ["$rowH", 12] }, "fill": "{tile}", "when": "mark" },
49
+ {
50
+ "id": "mark",
51
+ "type": "icon",
52
+ "when": "mark",
53
+ "markup": "{mark}",
54
+ "size": { "mul": ["$rowH", 0.56] },
55
+ "x": { "sub": [40, { "mul": ["$rowH", 0.28] }] },
56
+ "y": { "mul": ["$rowH", 1.22] },
57
+ "color": "$markInk"
58
+ },
59
+ { "id": "series", "type": "text", "content": { "if": "series", "then": "{series}", "else": "{name} — Series" }, "font": "Inter Tight", "weight": 700, "size": 7, "letterSpacing": 0, "lineHeight": 1.25, "x": 6, "y": 12, "maxWidth": 68, "color": "{ink}" },
60
+ { "id": "handle", "type": "text", "content": { "if": "handle", "then": "{handle}", "else": { "join": [["@@", { "lower": "{name}" }], ""] } }, "font": "Inter Tight", "weight": 500, "size": 7, "letterSpacing": 0, "lineHeight": 1.25, "x": 200, "y": 12, "align": "center", "maxWidth": 68, "color": "{ink}" },
61
+ { "id": "year", "type": "text", "content": "{year}", "when": "year", "font": "Inter Tight", "weight": 500, "size": 7, "letterSpacing": 0, "lineHeight": 1.25, "x": 394, "y": 12, "align": "right", "maxWidth": 68, "color": "{ink}" },
62
+ { "id": "label", "type": "text", "content": "{label}", "when": "label", "font": "Inter Tight", "weight": 700, "size": 7, "letterSpacing": 0, "lineHeight": 1.25, "x": 6, "y": { "add": ["$midTop", 12] }, "maxWidth": 68, "color": "{ink}" },
63
+ { "id": "code", "type": "text", "content": "{code}", "when": "code", "font": "Inter Tight", "weight": 500, "size": 7, "letterSpacing": 0, "lineHeight": 1.25, "x": 86, "y": { "add": ["$midTop", 12] }, "maxWidth": 68, "color": "{ink}" },
64
+ { "id": "coords", "type": "text", "content": "{coords}", "when": "coords", "font": "Inter Tight", "weight": 500, "size": 7, "letterSpacing": 0, "lineHeight": 1.25, "x": 394, "y": { "add": ["$midTop", 12] }, "align": "right", "maxWidth": 68, "color": "{ink}" },
65
+ {
66
+ "id": "headline",
67
+ "type": "text",
68
+ "content": "{headline}",
69
+ "transform": "uppercase",
70
+ "font": "Inter Tight",
71
+ "weight": 300,
72
+ "boldWeight": 800,
73
+ "size": 60,
74
+ "lineHeight": 0.92,
75
+ "letterSpacing": -0.035,
76
+ "fit": "width",
77
+ "maxWidth": 308,
78
+ "maxHeight": { "sub": [{ "mul": [{ "sub": ["$rows", { "add": ["$mid", 2] }] }, "$rowH"] }, 12] },
79
+ "anchor": "bottom-left",
80
+ "margin": 0,
81
+ "offset": { "x": 86, "y": { "mul": [-1, { "add": ["$rowH", 6] }] } },
82
+ "color": "{ink}"
83
+ },
84
+ { "id": "credit", "type": "text", "content": { "if": "credit", "then": "{credit}", "else": "Made by {name}" }, "font": "Inter Tight", "weight": 500, "size": 7, "letterSpacing": 0, "lineHeight": 1.25, "x": 6, "y": { "add": ["$footTop", 12] }, "maxWidth": 68, "color": "{ink}" },
85
+ { "id": "sub", "type": "text", "content": "{sub}", "when": "sub", "font": "Inter Tight", "weight": 600, "size": 10, "letterSpacing": -0.01, "lineHeight": 1.15, "x": 86, "y": { "add": ["$footTop", 13.5] }, "maxWidth": 228, "color": "{ink}" },
86
+ { "id": "save", "type": "text", "content": "{save}", "when": "save", "font": "Inter Tight", "weight": 500, "size": 7, "letterSpacing": 0, "lineHeight": 1.25, "x": 394, "y": { "add": ["$footTop", 12] }, "align": "right", "maxWidth": 68, "color": "{ink}" }
87
+ ]
88
+ },
89
+ { "id": "print", "type": "grain", "amount": 0.05, "size": 1.5 }
90
+ ]
91
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "../../schema/recipe.schema.json",
3
+ "name": "logofolio",
4
+ "description": "Logofolio, a collection sheet: a dark ground (or any colour), a small sub-title and a meta line across the top over a hairline, a big title fitted to the width, and the marks on an even grid of square cells, every mark optically sized the same (by its real bounds and how much it inks, not its 512 grid), centred so the margins match; columns and rows come from the count (3 × 3 up to 10 × 7) unless given. An optional footer line and the count sit under a second hairline. Drawn on a 400-wide grid as tall as the canvas, so it sets at 4:5, 1:1 and A-series portrait alike. For presenting a set, or a grid of marks for social.",
5
+ "requires": ["marks"],
6
+ "params": {
7
+ "title": "Logofolio",
8
+ "subtitle": "Selected work — Logo collection",
9
+ "meta": "Selected symbols 2020–2026",
10
+ "marks": [],
11
+ "cols": 0,
12
+ "rows": 0,
13
+ "footer": "",
14
+ "ground": "#0e0e0f",
15
+ "ink": "#f2efe8"
16
+ },
17
+ "vars": {
18
+ "h": { "mul": [400, { "div": ["@height", "@width"] }] },
19
+ "count": { "length": "{marks}" }
20
+ },
21
+ "layers": [
22
+ { "id": "ground", "type": "solid", "color": "{ground}" },
23
+ {
24
+ "id": "sheet",
25
+ "type": "group",
26
+ "viewBox": [0, 0, 400, "$h"],
27
+ "fit": "contain",
28
+ "layers": [
29
+ { "id": "subtitle", "type": "text", "content": "{subtitle}", "when": "subtitle", "font": "Inter Tight", "weight": 500, "size": 7.5, "letterSpacing": 0, "x": 16, "y": 24, "maxWidth": 200, "maxLines": 1, "color": "{ink}" },
30
+ { "id": "meta", "type": "text", "content": "{meta}", "when": "meta", "font": "Inter Tight", "weight": 500, "size": 7.5, "letterSpacing": 0, "x": 384, "y": 24, "align": "right", "maxWidth": 160, "maxLines": 1, "color": "{ink}", "opacity": 0.7 },
31
+ { "id": "rules", "type": "shape", "stroke": "{ink}", "strokeWidth": 0.5, "opacity": 0.35, "items": [
32
+ { "kind": "line", "x1": 16, "y1": 33, "x2": 384, "y2": 33 },
33
+ { "kind": "line", "x1": 16, "y1": { "sub": ["$h", 36] }, "x2": 384, "y2": { "sub": ["$h", 36] } }
34
+ ] },
35
+ {
36
+ "id": "title",
37
+ "type": "text",
38
+ "content": "{title}",
39
+ "transform": "uppercase",
40
+ "font": "Inter Tight",
41
+ "weight": 800,
42
+ "size": 60,
43
+ "lineHeight": 0.92,
44
+ "letterSpacing": -0.045,
45
+ "fit": "width",
46
+ "maxWidth": 368,
47
+ "maxHeight": 64,
48
+ "anchor": "top-left",
49
+ "margin": 0,
50
+ "offset": { "x": 16, "y": 46 },
51
+ "color": "{ink}"
52
+ },
53
+ { "id": "marks", "type": "marks", "marks": "{marks}", "cols": "{cols}", "rows": "{rows}", "x": 16, "y": 128, "width": 368, "height": { "sub": ["$h", 176] }, "size": 0.42, "color": "{ink}" },
54
+ { "id": "footer", "type": "text", "content": "{footer}", "when": "footer", "font": "Inter Tight", "weight": 500, "size": 7.5, "letterSpacing": 0, "x": 16, "y": { "sub": ["$h", 18] }, "maxWidth": 260, "maxLines": 1, "color": "{ink}" },
55
+ { "id": "count", "type": "text", "content": { "join": [["(", "$count", ")"], ""] }, "font": "Inter Tight", "weight": 500, "size": 7.5, "letterSpacing": 0, "x": 384, "y": { "sub": ["$h", 18] }, "align": "right", "color": "{ink}", "opacity": 0.7 }
56
+ ]
57
+ },
58
+ { "id": "print", "type": "grain", "amount": 0.035, "size": 1.5 }
59
+ ]
60
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "$schema": "../../schema/recipe.schema.json",
3
+ "name": "pixel-mosaic",
4
+ "description": "Pixel mosaic poster: a greyscale portrait full-bleed (or a grainy grey field with no photo), the mark enlarged to about 80% of the width and rastered into big square cells in one neon ink (lime by default) that follow the mark's own shape (@logomarks/core's pixelate), the photo showing between the cells and through a seeded few left out, a small asterisk or dot in one empty cell beside it, the name huge and bold across the foot fitted to the width, and a small meta row under it: a ® (or the mark, small), two short labels and a volume, right-aligned. Drawn on a 400-wide grid as tall as the canvas, so it sets at 4:5, 1:1 and 9:16; which cells drop out follows the seed.",
5
+ "params": {
6
+ "name": "Kestrel",
7
+ "labels": ["Visual identity", "Pixel series"],
8
+ "volume": "Vol. 116",
9
+ "badge": "®",
10
+ "neon": "#c8ff2e",
11
+ "ink": "#ffffff",
12
+ "cells": 11,
13
+ "drop": 0.12,
14
+ "accent": "asterisk"
15
+ },
16
+ "vars": {
17
+ "h": { "mul": [400, { "div": ["@height", "@width"] }] }
18
+ },
19
+ "layers": [
20
+ { "id": "photo", "type": "image", "when": "photo", "src": "{photo}", "fit": "cover", "grayscale": true },
21
+ { "id": "shade", "type": "linear", "when": "photo", "angle": 180, "colors": ["#000000", "#000000"], "opacities": [0, 0.55] },
22
+ { "id": "field", "type": "radial", "when": { "not": "photo" }, "colors": ["#6b6b6b", "#3a3a3a", "#141414"], "radius": "100vmax" },
23
+ { "id": "fieldGrain", "type": "grain", "when": { "not": "photo" }, "amount": 0.18, "vary": "edges" },
24
+ {
25
+ "id": "poster",
26
+ "type": "group",
27
+ "viewBox": [0, 0, 400, "$h"],
28
+ "fit": "contain",
29
+ "layers": [
30
+ { "id": "mosaic", "type": "mosaic", "when": "mark", "markup": "{mark}", "x": 40, "y": 28, "width": 320, "height": { "sub": ["$h", 164] }, "cells": "{cells}", "drop": "{drop}", "accent": "{accent}", "color": "{neon}" },
31
+ {
32
+ "id": "title",
33
+ "type": "text",
34
+ "content": "{name}",
35
+ "transform": "uppercase",
36
+ "font": "Inter Tight",
37
+ "weight": 900,
38
+ "size": 80,
39
+ "lineHeight": 0.9,
40
+ "letterSpacing": -0.05,
41
+ "fit": "width",
42
+ "maxWidth": 368,
43
+ "maxHeight": 92,
44
+ "anchor": "bottom-left",
45
+ "margin": 0,
46
+ "offset": { "x": 16, "y": -34 },
47
+ "color": "{ink}"
48
+ },
49
+ { "id": "badge", "type": "text", "when": { "all": ["badge", { "not": { "badge": "mark" } }] }, "content": "{badge}", "font": "Inter Tight", "weight": 600, "size": 9, "letterSpacing": 0, "x": 16, "y": { "sub": ["$h", 16] }, "color": "{ink}" },
50
+ { "id": "badgeMark", "type": "icon", "when": { "all": ["mark", { "badge": "mark" }] }, "markup": "{mark}", "x": 15, "y": { "sub": ["$h", 25.5] }, "size": 11, "color": "{ink}" },
51
+ { "id": "label1", "type": "text", "content": "{labels.0}", "when": "labels.0", "transform": "uppercase", "font": "Inter Tight", "weight": 600, "size": 6.5, "letterSpacing": 0.04, "x": 108, "y": { "sub": ["$h", 16] }, "maxWidth": 86, "maxLines": 1, "color": "{ink}" },
52
+ { "id": "label2", "type": "text", "content": "{labels.1}", "when": "labels.1", "transform": "uppercase", "font": "Inter Tight", "weight": 600, "size": 6.5, "letterSpacing": 0.04, "x": 200, "y": { "sub": ["$h", 16] }, "maxWidth": 86, "maxLines": 1, "color": "{ink}" },
53
+ { "id": "volume", "type": "text", "content": "{volume}", "when": "volume", "transform": "uppercase", "font": "Inter Tight", "weight": 600, "size": 6.5, "letterSpacing": 0.04, "x": 384, "y": { "sub": ["$h", 16] }, "align": "right", "maxWidth": 86, "maxLines": 1, "color": "{ink}" }
54
+ ]
55
+ },
56
+ { "id": "print", "type": "grain", "amount": 0.05, "size": 1.5 }
57
+ ]
58
+ }
@@ -0,0 +1,81 @@
1
+ {
2
+ "$schema": "../../schema/recipe.schema.json",
3
+ "name": "series-card",
4
+ "description": "Numbered series card, for a run of posts: one flat soft colour, a two-line uppercase title top-left, a large number bottom-left (\"01\"), and the mark large in the space left, solid or as contour lines (its outline and three to five more drawn round it, gap apart, merging where its parts meet). Set number and ground per post and keep the rest, and ten of them read as one series across a palette. An optional label top-right. Drawn on a 400-wide grid as tall as the canvas, so it sets at 1:1, 4:5, 9:16 and A-series portrait alike.",
5
+ "params": {
6
+ "title": ["Everything", "is connected"],
7
+ "number": "01",
8
+ "label": "",
9
+ "style": "contour",
10
+ "lines": 4,
11
+ "gap": 0.055,
12
+ "ground": "#f3c9b8",
13
+ "ink": "#1c1b19"
14
+ },
15
+ "vars": {
16
+ "h": { "mul": [400, { "div": ["@height", "@width"] }] }
17
+ },
18
+ "layers": [
19
+ { "id": "ground", "type": "solid", "color": "{ground}" },
20
+ {
21
+ "id": "card",
22
+ "type": "group",
23
+ "viewBox": [0, 0, 400, "$h"],
24
+ "fit": "contain",
25
+ "layers": [
26
+ {
27
+ "id": "mark",
28
+ "type": "outline",
29
+ "when": "mark",
30
+ "markup": "{mark}",
31
+ "lines": { "if": { "style": "solid" }, "then": 0, "else": "{lines}" },
32
+ "gap": "{gap}",
33
+ "stroke": 1.15,
34
+ "x": 124,
35
+ "y": 84,
36
+ "width": 260,
37
+ "height": { "sub": ["$h", 100] },
38
+ "color": "{ink}"
39
+ },
40
+ {
41
+ "id": "title",
42
+ "type": "text",
43
+ "content": "{title}",
44
+ "when": "title",
45
+ "transform": "uppercase",
46
+ "font": "Inter Tight",
47
+ "weight": 700,
48
+ "size": 21,
49
+ "lineHeight": 0.95,
50
+ "letterSpacing": -0.025,
51
+ "fit": "shrink",
52
+ "maxWidth": 250,
53
+ "maxLines": 2,
54
+ "anchor": "top-left",
55
+ "margin": 0,
56
+ "offset": { "x": 18, "y": 20 },
57
+ "color": "{ink}"
58
+ },
59
+ { "id": "label", "type": "text", "content": "{label}", "when": "label", "font": "Inter Tight", "weight": 500, "size": 7, "letterSpacing": 0, "x": 382, "y": 27, "align": "right", "maxWidth": 110, "maxLines": 2, "color": "{ink}" },
60
+ {
61
+ "id": "number",
62
+ "type": "text",
63
+ "content": "{number}",
64
+ "when": "number",
65
+ "font": "Inter Tight",
66
+ "weight": 600,
67
+ "size": 92,
68
+ "lineHeight": 0.8,
69
+ "letterSpacing": -0.06,
70
+ "fit": "shrink",
71
+ "maxWidth": 104,
72
+ "maxLines": 1,
73
+ "anchor": "bottom-left",
74
+ "margin": 0,
75
+ "offset": { "x": 14, "y": -16 },
76
+ "color": "{ink}"
77
+ }
78
+ ]
79
+ }
80
+ ]
81
+ }