grailwind 0.7.0 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,42 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.8.1] - 2026-11-13
10
+
11
+ ### Fixed
12
+
13
+ - Counterchanged charges `on` a counterchanged ordinary showed the ordinary's
14
+ swapped field and vanished. They now show the field's own tinctures.
15
+ - `chevronny-*` in a part of a split shield (a `per-pale` half, a `per-fess`
16
+ chief) broke at the centre line. It now follows the part, as `per-chevron`
17
+ does, and the arms meet.
18
+
19
+ ## [0.8.0] - 2026-11-12
20
+
21
+ ### Added
22
+
23
+ - Wavy ordinaries, e.g. `fess-wavy-or` and `three-bars-wavy-argent`.
24
+ - Indented and embattled ordinaries, e.g. `chief-indented-azure` and
25
+ `cross-embattled-gules`. A fess, bar, bend or chevron embattled has
26
+ battlements on its upper edge only. The chevron, cross, saltire, pall and
27
+ pile take them with hand-drawn corners, as do the wavy chevron and pall.
28
+
29
+ ### Changed
30
+
31
+ - Ordinary proportions move towards Fox-Davies: the chevron is wider (a fifth
32
+ of the shield), the cross's limbs are equal, the saltire is wider and crosses
33
+ at the fess point (as does `per-saltire-*`), and the pile is wider. The
34
+ pall's arms and foot are equal, and its arms meet above the fess point.
35
+ - The engrailed cross and pile are hand-drawn, with clean corners and tips.
36
+ - `@grailwind/eslint-plugin` 0.1.0-beta.4 needs `grailwind` 0.8.x.
37
+
38
+ ### Fixed
39
+
40
+ - `gyronny-*` crosses at the fess point, as `per-saltire-*` does, so a
41
+ counterchanged saltire lines up with it.
42
+ - Charges `on` an engrailed or indented ordinary are smaller, and charges
43
+ `between` a lined bend, pall, or embattled chevron move clear of its edge.
44
+
9
45
  ## [0.7.0] - 2026-11-09
10
46
 
11
47
  ### Added
@@ -157,7 +193,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
157
193
  Initial release: tinctures, divisions, ordinaries, charges, strict mode and
158
194
  custom SVG charges.
159
195
 
160
- [Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.7.0...HEAD
196
+ [Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.8.1...HEAD
197
+ [0.8.1]: https://github.com/pafirmin/grailwind/compare/v0.8.0...v0.8.1
198
+ [0.8.0]: https://github.com/pafirmin/grailwind/compare/v0.7.0...v0.8.0
161
199
  [0.7.0]: https://github.com/pafirmin/grailwind/compare/v0.6.0...v0.7.0
162
200
  [0.6.0]: https://github.com/pafirmin/grailwind/compare/v0.5.0...v0.6.0
163
201
  [0.5.0]: https://github.com/pafirmin/grailwind/compare/v0.4.0...v0.5.0
package/README.md CHANGED
@@ -61,7 +61,7 @@ tinctures replace Tailwind's colours entirely.
61
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/charges.js CHANGED
@@ -149,17 +149,28 @@ const inheritCounterchange = Object.fromEntries(
149
149
  COUNTERCHANGE.map((n) => [`--counterchange-${n}`, "inherit"]),
150
150
  );
151
151
 
152
+ // What a charge on a counterchanged ordinary is counterchanged against: the field's own
153
+ // tinctures, handed down by the ordinary to `on` containers only.
154
+ const inheritOnCounterchange = Object.fromEntries(
155
+ COUNTERCHANGE.map((n) => [`--on-counterchange-${n}`, "inherit"]),
156
+ );
157
+
152
158
  // A layer the size of the shield, as the escutcheon is the containing block
153
159
  // for fixed positioning. The charge's mask cuts it to shape.
154
160
  const counterchanged = {
155
161
  ...inheritCounterchange,
162
+ ...inheritOnCounterchange,
156
163
  "&::before": {
157
164
  content: '""',
158
165
  position: "fixed",
159
166
  inset: "0",
160
167
  ...inheritCounterchange,
168
+ ...inheritOnCounterchange,
161
169
  ...Object.fromEntries(
162
- COUNTERCHANGE.map((n) => [`background-${n}`, `var(--counterchange-${n})`]),
170
+ COUNTERCHANGE.map((n) => [
171
+ `background-${n}`,
172
+ `var(--on-counterchange-${n}, var(--counterchange-${n}))`,
173
+ ]),
163
174
  ),
164
175
  },
165
176
  };
@@ -279,7 +290,8 @@ export default plugin(({ addUtilities, matchUtilities }) => {
279
290
  };
280
291
  }
281
292
  }
282
- utilities[".on"] = on;
293
+ // Nested, so Tailwind keeps these apart from the per-count rules above.
294
+ utilities[".on"] = { "&": inheritOnCounterchange, ...on };
283
295
 
284
296
  for (const name of Object.keys(POINTS)) {
285
297
  utilities[`.in-${name}`] = { ".charges > &": atPoint(name) };
package/data.js CHANGED
@@ -80,7 +80,10 @@ export const ROUNDELS = {
80
80
  };
81
81
 
82
82
  // Lines of partition for an ordinary's edge, e.g. `fess-engrailed-or`.
83
- 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;
84
87
 
85
88
  // Ordinaries that can be voided, e.g. `cross-voided-argent`.
86
89
  export const VOIDABLE = [
@@ -143,15 +146,18 @@ export const BETWEEN = {
143
146
  "bend-sinister": { size: 24, points: [[30, 26], [70, 68]], counts: {
144
147
  6: { size: 14, points: [[52, 9], [24, 12], [16, 38], [91, 48], [82, 72], [62, 84]] } } },
145
148
  chevron: { size: 22, points: [[24, 22], [76, 22], [50, 70]], counts: {
146
- 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]] } } },
147
150
  cross: { size: 18, points: [[20, 17], [80, 17], [24, 72], [76, 72]] },
148
- saltire: { size: 16, points: [[50, 13], [14, 50], [86, 50], [50, 84]], counts: {
149
- 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]] } } },
150
153
  pall: { size: 18, points: [[50, 11], [18, 56], [82, 56]], counts: {
151
154
  2: { points: [[18, 56], [82, 56]] } } },
152
155
  pile: { size: 20, points: [[18, 55], [82, 55]] },
153
156
  };
154
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
+
155
161
  // Where charges go for "on", along the ordinary's centre line, by number of charges.
156
162
  // prettier-ignore
157
163
  export const ON = {
@@ -159,10 +165,10 @@ export const ON = {
159
165
  fess: { size: 22, counts: { 1: [[50, 45]], 2: [[30, 45], [70, 45]], 3: [[20, 45], [50, 45], [80, 45]] } },
160
166
  bend: { size: 18, counts: { 1: [[50, 50]], 2: [[33, 33], [67, 67]], 3: [[24, 24], [50, 50], [76, 76]] } },
161
167
  "bend-sinister": { size: 18, counts: { 1: [[50, 50]], 2: [[67, 33], [33, 67]], 3: [[76, 24], [50, 50], [24, 76]] } },
162
- 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]] } },
163
169
  cross: { size: 15, counts: { 1: [[50, 45]], 5: [[50, 15], [18, 45], [50, 45], [82, 45], [50, 80]] } },
164
- saltire: { size: 14, counts: { 1: [[50, 50]], 5: [[22, 22], [78, 22], [50, 50], [22, 78], [78, 78]] } },
165
- 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]] } },
166
172
  pile: { size: 14, counts: { 1: [[50, 25]], 3: [[50, 14], [50, 34], [50, 54]] } },
167
173
  };
168
174
 
@@ -187,6 +193,45 @@ for (const ordinary of REVERSIBLE) {
187
193
  // prettier-ignore
188
194
  BETWEEN["chevron-reversed"] = {
189
195
  ...BETWEEN["chevron-reversed"],
190
- points: [[28, 74], [72, 74], [50, 30]],
191
- 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
+ },
192
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 });
@@ -46,31 +50,36 @@ const bendy = (to, pieces) => (a, b) => {
46
50
  return layer(`linear-gradient(to ${to}, ${stops.join(", ")})`);
47
51
  };
48
52
 
49
- // The direction of `to bottom right` on the shield (aspect-ratio 5 / 6 in grailwind.css), so
50
- // chevronny's lines run parallel to per chevron's, the field's diagonals.
51
- const CHEVRON_ANGLE = 180 - (Math.atan(6 / 5) * 180) / Math.PI;
52
-
53
- // A half box is 75% as long as the whole along this angle (45/sqrt(61) over 60/sqrt(61)).
54
- // Plain, the sinister half sits over a full-width dexter layer, which hides the centre seam.
55
- // With a fur the layers can't overlap, as the transparent stripes would show the layer below.
53
+ // Four tiles, each half the box, so the lines follow the box's own proportions: a part of the
54
+ // shield is the whole one squeezed to fit, as per chevron's are. A tile's lines run parallel to its
55
+ // diagonal, dexter tiles `to bottom right` and sinister ones `to bottom left`.
56
+ // The period is 300/n of a tile's gradient line, which keeps a stripe's width on the whole shield.
57
+ // A lower tile starts half a gradient line further on, so its lines continue the upper tile's.
58
+ // Plain, the dexter and lower tiles overlap the others by 1px, hiding the seams. With a fur the
59
+ // transparent stripes would show the tile beneath, so they butt.
56
60
  const chevronny = (pieces) => (a, b, fur) => {
57
- const stripe = (angle, length) =>
58
- `repeating-linear-gradient(${angle}deg, ${a} 0 calc(${length}% / ${pieces}), ${b} 0 calc(${2 * length}% / ${pieces}))`;
59
- const sinister = stripe(-CHEVRON_ANGLE, 100);
60
-
61
- return fur
62
- ? {
63
- "background-image": [stripe(CHEVRON_ANGLE, 100), sinister].join(", "),
64
- "background-size": "50% 100%",
65
- "background-position": "left, right",
66
- "background-repeat": "no-repeat",
67
- }
68
- : {
69
- "background-image": [sinister, stripe(CHEVRON_ANGLE, 75)].join(", "),
70
- "background-size": "50% 100%, 100% 100%",
71
- "background-position": "right, left",
72
- "background-repeat": "no-repeat",
73
- };
61
+ const period = 300 / pieces;
62
+ const lowerStart = (period - (50 % period)) % period;
63
+ const stripe = (to, start) =>
64
+ `repeating-linear-gradient(to bottom ${to}, ${a} ${start}% ${start + period / 2}%, ${b} 0 ${start + period}%)`;
65
+ const overlap = fur ? "50%" : "calc(50% + 1px)";
66
+
67
+ return {
68
+ "background-image": [
69
+ stripe("left", 0),
70
+ stripe("left", lowerStart),
71
+ stripe("right", 0),
72
+ stripe("right", lowerStart),
73
+ ].join(", "),
74
+ "background-size": [
75
+ `50% 50%`,
76
+ `50% ${overlap}`,
77
+ `${overlap} 50%`,
78
+ `${overlap} ${overlap}`,
79
+ ].join(", "),
80
+ "background-position": "right top, right bottom, left top, left bottom",
81
+ "background-repeat": "no-repeat",
82
+ };
74
83
  };
75
84
 
76
85
  const DIVISIONS = {
@@ -78,7 +87,8 @@ const DIVISIONS = {
78
87
  "per-pale": (a, b) => layer(`linear-gradient(to right, ${a} 50%, ${b} 50%)`),
79
88
 
80
89
  // Horizontal split through the fess point: first in chief.
81
- "per-fess": (a, b) => layer(`linear-gradient(to bottom, ${a} ${FESS_POINT}, ${b} ${FESS_POINT})`),
90
+ "per-fess": (a, b) =>
91
+ layer(`linear-gradient(to bottom, ${a} ${FESS_POINT}%, ${b} ${FESS_POINT}%)`),
82
92
 
83
93
  // Line from dexter chief to sinister base: first in the upper half.
84
94
  // With a corner-to-corner gradient, the 50% line joins the other two corners.
@@ -106,9 +116,11 @@ const DIVISIONS = {
106
116
  ].join(", "),
107
117
  },
108
118
 
109
- // Both diagonals, crossing at the centre like the saltire: first in chief and base.
110
- // Gyronny's four tiles, with the top-right and bottom-left swapped. Two strips under them fill
111
- // the sub-pixel seam where the tiles meet: the first tincture down, the second across.
119
+ // Both lines of the saltire, from the top corners through the fess point to the flanks at 90%:
120
+ // first in chief and base. Four tiles, each half the width and 45% high, with each half-line
121
+ // as a tile's diagonal, and the base below them in the first tincture, 2px taller so Chromium
122
+ // leaves no seam where it meets them. Two strips under the tiles fill the sub-pixel seam where
123
+ // they meet: the first tincture down, the second across.
112
124
  "per-saltire": (a, b) => ({
113
125
  "background-image": [
114
126
  `linear-gradient(to bottom left, ${a} 50%, ${b} 50%)`,
@@ -116,10 +128,24 @@ const DIVISIONS = {
116
128
  `linear-gradient(to bottom left, ${b} 50%, ${a} 50%)`,
117
129
  `linear-gradient(to bottom right, ${b} 50%, ${a} 50%)`,
118
130
  `linear-gradient(${a}, ${a})`,
131
+ `linear-gradient(${a}, ${a})`,
119
132
  `linear-gradient(${b}, ${b})`,
120
133
  ].join(", "),
121
- "background-size": "50% 50%, 50% 50%, 50% 50%, 50% 50%, 2px 100%, 100% 2px",
122
- "background-position": "left top, right top, right bottom, left bottom, center, center",
134
+ "background-size": [
135
+ ...Array(4).fill(`50% ${FESS_POINT}%`),
136
+ `100% calc(${100 - 2 * FESS_POINT}% + 2px)`,
137
+ "2px 100%",
138
+ "100% 2px",
139
+ ].join(", "),
140
+ "background-position": [
141
+ "left top",
142
+ "right top",
143
+ `right ${BASE_TILES}%`,
144
+ `left ${BASE_TILES}%`,
145
+ "bottom",
146
+ "center",
147
+ `center ${FESS_POINT}%`,
148
+ ].join(", "),
123
149
  "background-repeat": "no-repeat",
124
150
  }),
125
151
 
@@ -128,14 +154,15 @@ const DIVISIONS = {
128
154
  // With a fur, the bands can't overlap, or the fur's transparent half shows the band below.
129
155
  quarterly: (a, b, fur) => ({
130
156
  "background-image": `linear-gradient(to right, ${a} 50%, ${b} 50%), linear-gradient(to right, ${b} 50%, ${a} 50%)`,
131
- "background-size": `100% ${FESS_POINT}, 100% ${fur ? `calc(100% - ${FESS_POINT})` : "100%"}`,
157
+ "background-size": `100% ${FESS_POINT}%, 100% ${fur ? `calc(100% - ${FESS_POINT}%)` : "100%"}`,
132
158
  "background-position": fur ? "top, bottom" : "top, top",
133
159
  "background-repeat": "no-repeat",
134
160
  }),
135
161
 
136
- // Gyronny of eight: each quarter split corner to corner. The first tincture
137
- // is in the gyron just dexter of the top line. The background colour fills
138
- // any sub-pixel seam between quarters.
162
+ // Gyronny of eight: quarterly and per saltire, all through the fess point. The first tincture
163
+ // is in the gyron just dexter of the top line. Per saltire's four tiles, then the base below
164
+ // them split per pale, 2px taller so no seam shows. The background colour fills any sub-pixel
165
+ // seam between tiles.
139
166
  gyronny: (a, b) => ({
140
167
  "background-color": b,
141
168
  "background-image": [
@@ -143,9 +170,19 @@ const DIVISIONS = {
143
170
  `linear-gradient(to bottom right, ${b} 50%, ${a} 50%)`,
144
171
  `linear-gradient(to bottom left, ${b} 50%, ${a} 50%)`,
145
172
  `linear-gradient(to bottom right, ${a} 50%, ${b} 50%)`,
173
+ `linear-gradient(to right, ${b} 50%, ${a} 50%)`,
174
+ ].join(", "),
175
+ "background-size": [
176
+ ...Array(4).fill(`50% ${FESS_POINT}%`),
177
+ `100% calc(${100 - 2 * FESS_POINT}% + 2px)`,
178
+ ].join(", "),
179
+ "background-position": [
180
+ "left top",
181
+ "right top",
182
+ `right ${BASE_TILES}%`,
183
+ `left ${BASE_TILES}%`,
184
+ "bottom",
146
185
  ].join(", "),
147
- "background-size": "50% 50%",
148
- "background-position": "left top, right top, right bottom, left bottom",
149
186
  "background-repeat": "no-repeat",
150
187
  }),
151
188
 
@@ -193,21 +230,70 @@ const toLayers = (rules) => {
193
230
  });
194
231
  };
195
232
 
196
- // A division with one fur: the fur's part is left transparent, over the fur's own layers.
197
- const withFur = (division, first, second, theme) => {
233
+ /**
234
+ * A division with one fur: the fur's part is left transparent, over the fur's own layers.
235
+ * @param {string} division The division's name.
236
+ * @param {string} first The first tincture or fur.
237
+ * @param {string} second The second tincture or fur; one of the two is a fur.
238
+ * @param {Function} theme Tailwind's theme function, for the spots' colour.
239
+ * @returns {{image: string, size: string, position: string, repeat: string}[]} The layers, top first.
240
+ */
241
+ const furredLayers = (division, first, second, theme) => {
198
242
  const fur = FURS[first] ? first : second;
199
243
  const paint = (t) => (t === fur ? "transparent" : `var(--color-${t})`);
200
- const all = [
244
+ return [
201
245
  ...toLayers(DIVISIONS[division](paint(first), paint(second), true)),
202
246
  ...furLayers(fur, theme),
203
247
  ];
204
- return Object.fromEntries(
248
+ };
249
+
250
+ /**
251
+ * Layers joined into one comma-separated value per property.
252
+ * @param {{image: string, size: string, position: string, repeat: string}[]} layers
253
+ * @param {string} prefix The property prefix, such as "background" or "--field".
254
+ * @returns {Object<string, string>} `<prefix>-image`, `-size`, `-position` and `-repeat`.
255
+ */
256
+ const joinLayers = (layers, prefix) =>
257
+ Object.fromEntries(
205
258
  ["image", "size", "position", "repeat"].map((p) => [
206
- `background-${p}`,
207
- all.map((layer) => layer[p]).join(", "),
259
+ `${prefix}-${p}`,
260
+ layers.map((layer) => layer[p]).join(", "),
208
261
  ]),
209
262
  );
210
- };
263
+
264
+ /**
265
+ * A division with one fur, as background rules.
266
+ * @param {string} division The division's name.
267
+ * @param {string} first The first tincture or fur.
268
+ * @param {string} second The second tincture or fur; one of the two is a fur.
269
+ * @param {Function} theme Tailwind's theme function, for the spots' colour.
270
+ * @returns {Object<string, string>} The four background-* properties.
271
+ */
272
+ const withFur = (division, first, second, theme) =>
273
+ joinLayers(furredLayers(division, first, second, theme), "background");
274
+
275
+ /**
276
+ * The field's own layers as --field-* variables, for charges on a counterchanged ordinary.
277
+ * A background colour becomes a last opaque layer, as the variables carry no colour.
278
+ * @param {{image: string, size: string, position: string, repeat: string}[]} layers
279
+ * @param {string} [color] The division's background-color, if it has one.
280
+ * @returns {Object<string, string>} The four --field-* variables.
281
+ */
282
+ const field = (layers, color) =>
283
+ joinLayers(
284
+ color
285
+ ? [
286
+ ...layers,
287
+ {
288
+ image: `linear-gradient(${color}, ${color})`,
289
+ size: "100% 100%",
290
+ position: "0 0",
291
+ repeat: "no-repeat",
292
+ },
293
+ ]
294
+ : layers,
295
+ "--field",
296
+ );
211
297
 
212
298
  // The field with its tinctures swapped, for counterchanged ordinaries to paint.
213
299
  const counterchange = (rules) =>
@@ -230,8 +316,10 @@ export default plugin(({ addUtilities, theme }) => {
230
316
 
231
317
  const a = `var(--color-${first})`;
232
318
  const b = `var(--color-${second})`;
319
+ const unswapped = background(a, b);
233
320
  utilities[`.${division}-${first}-${second}`] = {
234
- ...background(a, b),
321
+ ...unswapped,
322
+ ...field(toLayers(unswapped), unswapped["background-color"]),
235
323
  ...counterchange(background(b, a)),
236
324
  "--field-first": a,
237
325
  };
@@ -247,9 +335,11 @@ export default plugin(({ addUtilities, theme }) => {
247
335
  [fur, tincture],
248
336
  [tincture, fur],
249
337
  ]) {
338
+ const layers = furredLayers(division, first, second, theme);
250
339
  // Only a tincture can be "of the first"; a fur first leaves it unset.
251
340
  utilities[`.${division}-${first}-${second}`] = {
252
- ...withFur(division, first, second, theme),
341
+ ...joinLayers(layers, "background"),
342
+ ...field(layers),
253
343
  ...counterchange(withFur(division, second, first, theme)),
254
344
  ...(!FURS[first] && { "--field-first": `var(--color-${first})` }),
255
345
  };
package/grailwind.css CHANGED
@@ -42,6 +42,42 @@
42
42
  syntax: "*";
43
43
  inherits: false;
44
44
  }
45
+ @property --field-image {
46
+ syntax: "*";
47
+ inherits: false;
48
+ }
49
+ @property --field-size {
50
+ syntax: "*";
51
+ inherits: false;
52
+ }
53
+ @property --field-position {
54
+ syntax: "*";
55
+ inherits: false;
56
+ }
57
+ @property --field-repeat {
58
+ syntax: "*";
59
+ inherits: false;
60
+ }
61
+ @property --on-counterchange-color {
62
+ syntax: "*";
63
+ inherits: false;
64
+ }
65
+ @property --on-counterchange-image {
66
+ syntax: "*";
67
+ inherits: false;
68
+ }
69
+ @property --on-counterchange-size {
70
+ syntax: "*";
71
+ inherits: false;
72
+ }
73
+ @property --on-counterchange-position {
74
+ syntax: "*";
75
+ inherits: false;
76
+ }
77
+ @property --on-counterchange-repeat {
78
+ syntax: "*";
79
+ inherits: false;
80
+ }
45
81
  /* A field's first tincture. The escutcheon copies it to --the-first, which inherits,
46
82
  * so a canton's own field can't change it. */
47
83
  @property --field-first {