grailwind 0.6.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -6,6 +6,51 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.8.0] - 2026-11-12
10
+
11
+ ### Added
12
+
13
+ - Wavy ordinaries, e.g. `fess-wavy-or` and `three-bars-wavy-argent`.
14
+ - Indented and embattled ordinaries, e.g. `chief-indented-azure` and
15
+ `cross-embattled-gules`. A fess, bar, bend or chevron embattled has
16
+ battlements on its upper edge only. The chevron, cross, saltire, pall and
17
+ pile take them with hand-drawn corners, as do the wavy chevron and pall.
18
+
19
+ ### Changed
20
+
21
+ - Ordinary proportions move towards Fox-Davies: the chevron is wider (a fifth
22
+ of the shield), the cross's limbs are equal, the saltire is wider and crosses
23
+ at the fess point (as does `per-saltire-*`), and the pile is wider. The
24
+ pall's arms and foot are equal, and its arms meet above the fess point.
25
+ - The engrailed cross and pile are hand-drawn, with clean corners and tips.
26
+ - `@grailwind/eslint-plugin` 0.1.0-beta.4 needs `grailwind` 0.8.x.
27
+
28
+ ### Fixed
29
+
30
+ - `gyronny-*` crosses at the fess point, as `per-saltire-*` does, so a
31
+ counterchanged saltire lines up with it.
32
+ - Charges `on` an engrailed or indented ordinary are smaller, and charges
33
+ `between` a lined bend, pall, or embattled chevron move clear of its edge.
34
+
35
+ ## [0.7.0] - 2026-11-09
36
+
37
+ ### Added
38
+
39
+ - `per-chevron-*`: a field divided along a chevron's centre line, the first
40
+ tincture in chief.
41
+ - `bendy-*`, `bendy-sinister-*`, `chevronny-*` (each with `-of-eight-*`) and
42
+ `chequy-*`: six or eight pieces, or five squares by six.
43
+
44
+ ### Changed
45
+
46
+ - `@grailwind/eslint-plugin` 0.1.0-beta.3 needs `grailwind` 0.7.x.
47
+
48
+ ### Fixed
49
+
50
+ - On a shield, `quarterly` and two-part `per-fess` split at the fess point, as
51
+ the divisions do, not halfway down.
52
+ - No seam down the centre of `per-saltire-*` at odd widths.
53
+
9
54
  ## [0.6.0] - 2026-11-06
10
55
 
11
56
  ### Added
@@ -138,7 +183,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
138
183
  Initial release: tinctures, divisions, ordinaries, charges, strict mode and
139
184
  custom SVG charges.
140
185
 
141
- [Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.6.0...HEAD
186
+ [Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.8.0...HEAD
187
+ [0.8.0]: https://github.com/pafirmin/grailwind/compare/v0.7.0...v0.8.0
188
+ [0.7.0]: https://github.com/pafirmin/grailwind/compare/v0.6.0...v0.7.0
142
189
  [0.6.0]: https://github.com/pafirmin/grailwind/compare/v0.5.0...v0.6.0
143
190
  [0.5.0]: https://github.com/pafirmin/grailwind/compare/v0.4.0...v0.5.0
144
191
  [0.4.0]: https://github.com/pafirmin/grailwind/compare/v0.3.0...v0.4.0
package/README.md CHANGED
@@ -57,11 +57,11 @@ tinctures replace Tailwind's colours entirely.
57
57
  - **The shield:** `escutcheon`, plus `bordure-*`, `inescutcheon` (a small shield at the centre)
58
58
  and `canton` (a ninth of the shield in dexter chief), each with its own field and charges.
59
59
  - **Divided fields:** `per-pale-or-gules`, `quarterly-azure-argent`,
60
- `gyronny-or-sable`, `barry-argent-azure` and more, or `per-pale`, `per-fess` and `quarterly` for
61
- layout.
60
+ `gyronny-or-sable`, `barry-argent-azure`, `bendy-argent-gules`, `chequy-or-azure` and more, or
61
+ `per-pale`, `per-fess` and `quarterly` for layout.
62
62
  - **Ordinaries:** `fess-or`, `chevron-gules`, `cross-argent`, `chief-azure`,
63
63
  `two-bars-or` and more, with `fimbriated-*` for a narrow border,
64
- `fess-engrailed-or` and the like for an engrailed or invected edge,
64
+ `fess-engrailed-or` and the like for an engrailed, invected, wavy, indented or embattled edge,
65
65
  `cross-voided-argent` and the like to show the field through it,
66
66
  `pile-reversed-or` (also the chevron and pall) to turn one upside down,
67
67
  `chief-ermine` and the like in a fur, or `chevron-counterchanged` and the like
package/data.js CHANGED
@@ -13,6 +13,7 @@ export const DIVISIONS = [
13
13
  "per-fess",
14
14
  "per-bend",
15
15
  "per-bend-sinister",
16
+ "per-chevron",
16
17
  "per-saltire",
17
18
  "quarterly",
18
19
  "gyronny",
@@ -21,6 +22,13 @@ export const DIVISIONS = [
21
22
  "barry-of-ten",
22
23
  "paly",
23
24
  "paly-of-eight",
25
+ "bendy",
26
+ "bendy-of-eight",
27
+ "bendy-sinister",
28
+ "bendy-sinister-of-eight",
29
+ "chevronny",
30
+ "chevronny-of-eight",
31
+ "chequy",
24
32
  ];
25
33
 
26
34
  // Ordinaries that can be reversed, e.g. `pile-reversed-or`: the same shape upside down.
@@ -72,7 +80,10 @@ export const ROUNDELS = {
72
80
  };
73
81
 
74
82
  // Lines of partition for an ordinary's edge, e.g. `fess-engrailed-or`.
75
- export const LINES = ["engrailed", "invected"];
83
+ export const LINES = ["engrailed", "invected", "wavy", "indented", "embattled"];
84
+
85
+ // The ordinaries that take a line: every one, the cornered ones drawn by hand (064).
86
+ export const lineOrdinaries = () => ORDINARIES;
76
87
 
77
88
  // Ordinaries that can be voided, e.g. `cross-voided-argent`.
78
89
  export const VOIDABLE = [
@@ -135,15 +146,18 @@ export const BETWEEN = {
135
146
  "bend-sinister": { size: 24, points: [[30, 26], [70, 68]], counts: {
136
147
  6: { size: 14, points: [[52, 9], [24, 12], [16, 38], [91, 48], [82, 72], [62, 84]] } } },
137
148
  chevron: { size: 22, points: [[24, 22], [76, 22], [50, 70]], counts: {
138
- 6: { size: 12, points: [[18, 14], [50, 11], [82, 14], [38, 66], [62, 66], [50, 84]] } } },
149
+ 6: { size: 12, points: [[18, 14], [50, 11], [82, 14], [40, 72], [60, 72], [50, 84]] } } },
139
150
  cross: { size: 18, points: [[20, 17], [80, 17], [24, 72], [76, 72]] },
140
- saltire: { size: 16, points: [[50, 13], [14, 50], [86, 50], [50, 84]], counts: {
141
- 2: { points: [[14, 50], [86, 50]] } } },
151
+ saltire: { size: 16, points: [[50, 13], [14, 45], [86, 45], [50, 84]], counts: {
152
+ 2: { points: [[14, 45], [86, 45]] } } },
142
153
  pall: { size: 18, points: [[50, 11], [18, 56], [82, 56]], counts: {
143
154
  2: { points: [[18, 56], [82, 56]] } } },
144
155
  pile: { size: 20, points: [[18, 55], [82, 55]] },
145
156
  };
146
157
 
158
+ // `on` charges shrink on lines that cut deep into the ordinary, by line.
159
+ export const ON_SCALE = { engrailed: 0.8, indented: 0.9 };
160
+
147
161
  // Where charges go for "on", along the ordinary's centre line, by number of charges.
148
162
  // prettier-ignore
149
163
  export const ON = {
@@ -151,10 +165,10 @@ export const ON = {
151
165
  fess: { size: 22, counts: { 1: [[50, 45]], 2: [[30, 45], [70, 45]], 3: [[20, 45], [50, 45], [80, 45]] } },
152
166
  bend: { size: 18, counts: { 1: [[50, 50]], 2: [[33, 33], [67, 67]], 3: [[24, 24], [50, 50], [76, 76]] } },
153
167
  "bend-sinister": { size: 18, counts: { 1: [[50, 50]], 2: [[67, 33], [33, 67]], 3: [[76, 24], [50, 50], [24, 76]] } },
154
- chevron: { size: 12, counts: { 1: [[50, 36]], 3: [[20, 65], [50, 36], [80, 65]] } },
168
+ chevron: { size: 15, counts: { 1: [[50, 36]], 3: [[20, 65], [50, 36], [80, 65]] } },
155
169
  cross: { size: 15, counts: { 1: [[50, 45]], 5: [[50, 15], [18, 45], [50, 45], [82, 45], [50, 80]] } },
156
- saltire: { size: 14, counts: { 1: [[50, 50]], 5: [[22, 22], [78, 22], [50, 50], [22, 78], [78, 78]] } },
157
- pall: { size: 14, counts: { 1: [[50, 50]], 3: [[22, 22], [78, 22], [50, 75]] } },
170
+ saltire: { size: 16, counts: { 1: [[50, 45]], 5: [[22, 19.8], [78, 19.8], [50, 45], [22, 70.2], [78, 70.2]] } },
171
+ pall: { size: 16, counts: { 1: [[50, 40]], 3: [[26, 20.8], [74, 20.8], [50, 75]] } },
158
172
  pile: { size: 14, counts: { 1: [[50, 25]], 3: [[50, 14], [50, 34], [50, 54]] } },
159
173
  };
160
174
 
@@ -179,6 +193,45 @@ for (const ordinary of REVERSIBLE) {
179
193
  // prettier-ignore
180
194
  BETWEEN["chevron-reversed"] = {
181
195
  ...BETWEEN["chevron-reversed"],
182
- points: [[28, 74], [72, 74], [50, 30]],
183
- counts: { 6: { size: 12, points: [[26, 78], [50, 89], [74, 78], [38, 34], [62, 34], [50, 16]] } },
196
+ points: [[26, 76], [74, 76], [50, 30]],
197
+ counts: { 6: { size: 12, points: [[26, 78], [50, 89], [74, 78], [40, 28], [60, 28], [50, 16]] } },
198
+ };
199
+
200
+ // `between` overrides where a line's edge hits a charge, by ordinary and line; the rest falls
201
+ // back to the plain ordinary's. The sinister bend and the reversed pall are mirrored from these.
202
+ // prettier-ignore
203
+ export const BETWEEN_LINED = {
204
+ bend: {
205
+ engrailed: { points: [[70, 26], [24, 72]] },
206
+ invected: { points: [[70, 26], [24, 72]] },
207
+ wavy: { points: [[70, 26], [24, 72]] },
208
+ indented: { points: [[70, 26], [24, 72]] },
209
+ },
210
+ chevron: {
211
+ embattled: { counts: { 6: { size: 10, points: [[16, 13], [50, 8], [84, 13], [40, 72], [60, 72], [50, 84]] } } },
212
+ },
213
+ "chevron-reversed": {
214
+ embattled: { points: [[26, 76], [74, 76], [50, 24]] },
215
+ },
216
+ pall: {
217
+ invected: { points: [[50, 8], [18, 56], [82, 56]] },
218
+ wavy: { points: [[50, 8], [18, 56], [82, 56]] },
219
+ indented: { points: [[50, 8], [18, 56], [82, 56]] },
220
+ embattled: { points: [[50, 8], [18, 56], [82, 56]] },
221
+ },
184
222
  };
223
+
224
+ const mirrorX = (points) => points.map(([x, y]) => [100 - x, y]);
225
+ const mirrorLined = (entry, flip) => ({
226
+ ...entry,
227
+ ...(entry.points && { points: flip(entry.points) }),
228
+ ...(entry.counts && {
229
+ counts: mapValues(entry.counts, (count) => ({ ...count, points: flip(count.points) })),
230
+ }),
231
+ });
232
+ BETWEEN_LINED["bend-sinister"] = mapValues(BETWEEN_LINED.bend, (entry) =>
233
+ mirrorLined(entry, mirrorX),
234
+ );
235
+ BETWEEN_LINED["pall-reversed"] = mapValues(BETWEEN_LINED.pall, (entry) =>
236
+ mirrorLined(entry, mirror),
237
+ );
package/divisions.js CHANGED
@@ -5,8 +5,12 @@ import plugin from "tailwindcss/plugin";
5
5
  import { FURS, TINCTURES } from "./data.js";
6
6
  import { furLayers } from "./furs.js";
7
7
 
8
- // Horizontal lines of partition pass through the fess point, as the cross and fess do.
9
- const FESS_POINT = "45%";
8
+ // The fess point, in % down the shield: horizontal lines of partition pass through it, as the
9
+ // cross and fess do.
10
+ const FESS_POINT = 45;
11
+
12
+ // The background-position that puts a tile FESS_POINT% high just below the fess point.
13
+ const BASE_TILES = ((100 * FESS_POINT) / (100 - FESS_POINT)).toFixed(2);
10
14
 
11
15
  // Each division maps (first, second) tincture colours to background rules.
12
16
  const layer = (image) => ({ "background-image": image });
@@ -22,12 +26,64 @@ const stripes = (to, pieces) => (a, b) => {
22
26
  };
23
27
  };
24
28
 
29
+ // How much of the corner-to-corner extent the shield covers: the base corner is cut away by the
30
+ // border-radius of 0 0 50% 50% / 0 0 45% 45% and the 5:6 aspect ratio in grailwind.css. It runs
31
+ // from the chief corner the bands start in to the tangent on the base curve.
32
+ const BENDY_VISIBLE = (() => {
33
+ const [width, height] = [5, 6];
34
+ const [radiusX, radiusY] = [width / 2, 0.45 * height];
35
+ const [centreX, centreY] = [width / 2, height - radiusY];
36
+ const tangent = Math.sqrt((height * radiusX) ** 2 + (width * radiusY) ** 2);
37
+ return (width * height - height * centreX + width * centreY + tangent) / (2 * width * height);
38
+ })();
39
+
40
+ // Diagonal bands parallel to per bend, `pieces` equal ones across the visible part of the shield,
41
+ // the last running to the edge. Explicit stops, not a repeating gradient, so a canton's square
42
+ // corners don't show the first tincture again.
43
+ const bendy = (to, pieces) => (a, b) => {
44
+ const stops = [];
45
+ for (let i = 0; i < pieces; i++) {
46
+ const start = i === 0 ? 0 : ((i * BENDY_VISIBLE * 100) / pieces).toFixed(2);
47
+ const end = i === pieces - 1 ? 100 : (((i + 1) * BENDY_VISIBLE * 100) / pieces).toFixed(2);
48
+ stops.push(`${i % 2 === 0 ? a : b} ${start}% ${end}%`);
49
+ }
50
+ return layer(`linear-gradient(to ${to}, ${stops.join(", ")})`);
51
+ };
52
+
53
+ // The direction of `to bottom right` on the shield (aspect-ratio 5 / 6 in grailwind.css), so
54
+ // chevronny's lines run parallel to per chevron's, the field's diagonals.
55
+ const CHEVRON_ANGLE = 180 - (Math.atan(6 / 5) * 180) / Math.PI;
56
+
57
+ // A half box is 75% as long as the whole along this angle (45/sqrt(61) over 60/sqrt(61)).
58
+ // Plain, the sinister half sits over a full-width dexter layer, which hides the centre seam.
59
+ // With a fur the layers can't overlap, as the transparent stripes would show the layer below.
60
+ const chevronny = (pieces) => (a, b, fur) => {
61
+ const stripe = (angle, length) =>
62
+ `repeating-linear-gradient(${angle}deg, ${a} 0 calc(${length}% / ${pieces}), ${b} 0 calc(${2 * length}% / ${pieces}))`;
63
+ const sinister = stripe(-CHEVRON_ANGLE, 100);
64
+
65
+ return fur
66
+ ? {
67
+ "background-image": [stripe(CHEVRON_ANGLE, 100), sinister].join(", "),
68
+ "background-size": "50% 100%",
69
+ "background-position": "left, right",
70
+ "background-repeat": "no-repeat",
71
+ }
72
+ : {
73
+ "background-image": [sinister, stripe(CHEVRON_ANGLE, 75)].join(", "),
74
+ "background-size": "50% 100%, 100% 100%",
75
+ "background-position": "right, left",
76
+ "background-repeat": "no-repeat",
77
+ };
78
+ };
79
+
25
80
  const DIVISIONS = {
26
81
  // Vertical split: first on the dexter (viewer's left).
27
82
  "per-pale": (a, b) => layer(`linear-gradient(to right, ${a} 50%, ${b} 50%)`),
28
83
 
29
84
  // Horizontal split through the fess point: first in chief.
30
- "per-fess": (a, b) => layer(`linear-gradient(to bottom, ${a} ${FESS_POINT}, ${b} ${FESS_POINT})`),
85
+ "per-fess": (a, b) =>
86
+ layer(`linear-gradient(to bottom, ${a} ${FESS_POINT}%, ${b} ${FESS_POINT}%)`),
31
87
 
32
88
  // Line from dexter chief to sinister base: first in the upper half.
33
89
  // With a corner-to-corner gradient, the 50% line joins the other two corners.
@@ -36,18 +92,55 @@ const DIVISIONS = {
36
92
  // Line from sinister chief to dexter base: first in the upper half.
37
93
  "per-bend-sinister": (a, b) => layer(`linear-gradient(to bottom right, ${a} 50%, ${b} 50%)`),
38
94
 
39
- // Both diagonals, crossing at the centre like the saltire: first in chief and base.
40
- // Gyronny's four tiles, with the top-right and bottom-left swapped.
95
+ "per-chevron": (a, b) =>
96
+ a === "transparent"
97
+ ? {
98
+ "background-image": [
99
+ `linear-gradient(to bottom right, ${a} 50%, ${b} 50%)`,
100
+ `linear-gradient(to bottom left, ${a} 50%, ${b} 50%)`,
101
+ `linear-gradient(${b}, ${b})`,
102
+ ].join(", "),
103
+ "background-size": "50% 50%, 50% 50%, 100% 15%",
104
+ "background-position": "left 70%, right 70%, bottom",
105
+ "background-repeat": "no-repeat",
106
+ }
107
+ : {
108
+ "background-image": [
109
+ `linear-gradient(to bottom right, ${a} 42.5%, transparent 42.5%)`,
110
+ `linear-gradient(to bottom left, ${a} 42.5%, ${b} 42.5%)`,
111
+ ].join(", "),
112
+ },
113
+
114
+ // Both lines of the saltire, from the top corners through the fess point to the flanks at 90%:
115
+ // first in chief and base. Four tiles, each half the width and 45% high, with each half-line
116
+ // as a tile's diagonal, and the base below them in the first tincture, 2px taller so Chromium
117
+ // leaves no seam where it meets them. Two strips under the tiles fill the sub-pixel seam where
118
+ // they meet: the first tincture down, the second across.
41
119
  "per-saltire": (a, b) => ({
42
- "background-color": b,
43
120
  "background-image": [
44
121
  `linear-gradient(to bottom left, ${a} 50%, ${b} 50%)`,
45
122
  `linear-gradient(to bottom right, ${a} 50%, ${b} 50%)`,
46
123
  `linear-gradient(to bottom left, ${b} 50%, ${a} 50%)`,
47
124
  `linear-gradient(to bottom right, ${b} 50%, ${a} 50%)`,
125
+ `linear-gradient(${a}, ${a})`,
126
+ `linear-gradient(${a}, ${a})`,
127
+ `linear-gradient(${b}, ${b})`,
128
+ ].join(", "),
129
+ "background-size": [
130
+ ...Array(4).fill(`50% ${FESS_POINT}%`),
131
+ `100% calc(${100 - 2 * FESS_POINT}% + 2px)`,
132
+ "2px 100%",
133
+ "100% 2px",
134
+ ].join(", "),
135
+ "background-position": [
136
+ "left top",
137
+ "right top",
138
+ `right ${BASE_TILES}%`,
139
+ `left ${BASE_TILES}%`,
140
+ "bottom",
141
+ "center",
142
+ `center ${FESS_POINT}%`,
48
143
  ].join(", "),
49
- "background-size": "50% 50%",
50
- "background-position": "left top, right top, right bottom, left bottom",
51
144
  "background-repeat": "no-repeat",
52
145
  }),
53
146
 
@@ -56,14 +149,15 @@ const DIVISIONS = {
56
149
  // With a fur, the bands can't overlap, or the fur's transparent half shows the band below.
57
150
  quarterly: (a, b, fur) => ({
58
151
  "background-image": `linear-gradient(to right, ${a} 50%, ${b} 50%), linear-gradient(to right, ${b} 50%, ${a} 50%)`,
59
- "background-size": `100% ${FESS_POINT}, 100% ${fur ? `calc(100% - ${FESS_POINT})` : "100%"}`,
152
+ "background-size": `100% ${FESS_POINT}%, 100% ${fur ? `calc(100% - ${FESS_POINT}%)` : "100%"}`,
60
153
  "background-position": fur ? "top, bottom" : "top, top",
61
154
  "background-repeat": "no-repeat",
62
155
  }),
63
156
 
64
- // Gyronny of eight: each quarter split corner to corner. The first tincture
65
- // is in the gyron just dexter of the top line. The background colour fills
66
- // any sub-pixel seam between quarters.
157
+ // Gyronny of eight: quarterly and per saltire, all through the fess point. The first tincture
158
+ // is in the gyron just dexter of the top line. Per saltire's four tiles, then the base below
159
+ // them split per pale, 2px taller so no seam shows. The background colour fills any sub-pixel
160
+ // seam between tiles.
67
161
  gyronny: (a, b) => ({
68
162
  "background-color": b,
69
163
  "background-image": [
@@ -71,9 +165,19 @@ const DIVISIONS = {
71
165
  `linear-gradient(to bottom right, ${b} 50%, ${a} 50%)`,
72
166
  `linear-gradient(to bottom left, ${b} 50%, ${a} 50%)`,
73
167
  `linear-gradient(to bottom right, ${a} 50%, ${b} 50%)`,
168
+ `linear-gradient(to right, ${b} 50%, ${a} 50%)`,
169
+ ].join(", "),
170
+ "background-size": [
171
+ ...Array(4).fill(`50% ${FESS_POINT}%`),
172
+ `100% calc(${100 - 2 * FESS_POINT}% + 2px)`,
173
+ ].join(", "),
174
+ "background-position": [
175
+ "left top",
176
+ "right top",
177
+ `right ${BASE_TILES}%`,
178
+ `left ${BASE_TILES}%`,
179
+ "bottom",
74
180
  ].join(", "),
75
- "background-size": "50% 50%",
76
- "background-position": "left top, right top, right bottom, left bottom",
77
181
  "background-repeat": "no-repeat",
78
182
  }),
79
183
 
@@ -83,22 +187,42 @@ const DIVISIONS = {
83
187
  "barry-of-ten": stripes("bottom", 10),
84
188
  paly: stripes("right", 6),
85
189
  "paly-of-eight": stripes("right", 8),
190
+
191
+ // Bendy has the first tincture in the sinister chief corner, bendy sinister in the dexter.
192
+ bendy: bendy("bottom left", 6),
193
+ "bendy-of-eight": bendy("bottom left", 8),
194
+ "bendy-sinister": bendy("bottom right", 6),
195
+ "bendy-sinister-of-eight": bendy("bottom right", 8),
196
+
197
+ // The first tincture in the chief corners.
198
+ chevronny: chevronny(6),
199
+ "chevronny-of-eight": chevronny(8),
200
+
201
+ // 5 across and 6 down, the first tincture in the dexter chief square. Conic starts at the top.
202
+ // The repeat must be explicit, as with stripes.
203
+ chequy: (a, b) => ({
204
+ "background-image": `conic-gradient(${b} 0 25%, ${a} 0 50%, ${b} 0 75%, ${a} 0)`,
205
+ "background-size": "40% calc(100% / 3)",
206
+ "background-repeat": "repeat",
207
+ }),
86
208
  };
87
209
 
88
210
  // A division's background rules as one { image, size, position, repeat } per layer.
89
211
  const toLayers = (rules) => {
90
- return rules["background-image"].split(/, (?=linear-gradient)/).map((gradient, i) => {
91
- const size = (rules["background-size"] ?? "100% 100%").split(", ");
92
- const position = (rules["background-position"] ?? "0 0").split(", ");
93
- const repeat = (rules["background-repeat"] ?? "no-repeat").split(", ");
94
-
95
- return {
96
- image: gradient,
97
- size: size[i % size.length],
98
- position: position[i % position.length],
99
- repeat: repeat[i % repeat.length],
100
- };
101
- });
212
+ return rules["background-image"]
213
+ .split(/, (?=(?:repeating-)?(?:linear|conic)-gradient)/)
214
+ .map((gradient, i) => {
215
+ const size = (rules["background-size"] ?? "100% 100%").split(", ");
216
+ const position = (rules["background-position"] ?? "0 0").split(", ");
217
+ const repeat = (rules["background-repeat"] ?? "no-repeat").split(", ");
218
+
219
+ return {
220
+ image: gradient,
221
+ size: size[i % size.length],
222
+ position: position[i % position.length],
223
+ repeat: repeat[i % repeat.length],
224
+ };
225
+ });
102
226
  };
103
227
 
104
228
  // A division with one fur: the fur's part is left transparent, over the fur's own layers.
package/grailwind.css CHANGED
@@ -135,10 +135,18 @@
135
135
  grid-auto-flow: row;
136
136
  grid-auto-rows: 1fr;
137
137
 
138
- /* See per-pale. */
139
- &:is(.escutcheon, .escutcheon *) > * {
140
- position: relative;
141
- contain: layout;
138
+ /* On a shield, two parts split at the fess point, FESS_POINT in divisions.js.
139
+ * More parts stay equal. */
140
+ &:is(.escutcheon, .escutcheon *) {
141
+ &:has(> :last-child:nth-child(2)) {
142
+ grid-template-rows: 45fr 55fr;
143
+ }
144
+
145
+ /* See per-pale. */
146
+ & > * {
147
+ position: relative;
148
+ contain: layout;
149
+ }
142
150
  }
143
151
  }
144
152
 
@@ -155,10 +163,15 @@
155
163
  --outer-inset: 0px;
156
164
  }
157
165
 
158
- /* See per-pale. */
159
- &:is(.escutcheon, .escutcheon *) > * {
160
- position: relative;
161
- contain: layout;
166
+ /* On a shield, split at the fess point, as per-fess. */
167
+ &:is(.escutcheon, .escutcheon *) {
168
+ grid-template-rows: 45fr 55fr;
169
+
170
+ /* See per-pale. */
171
+ & > * {
172
+ position: relative;
173
+ contain: layout;
174
+ }
162
175
  }
163
176
  }
164
177