grailwind 0.7.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,32 @@ 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
+
9
35
  ## [0.7.0] - 2026-11-09
10
36
 
11
37
  ### Added
@@ -157,7 +183,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
157
183
  Initial release: tinctures, divisions, ordinaries, charges, strict mode and
158
184
  custom SVG charges.
159
185
 
160
- [Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.7.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
161
188
  [0.7.0]: https://github.com/pafirmin/grailwind/compare/v0.6.0...v0.7.0
162
189
  [0.6.0]: https://github.com/pafirmin/grailwind/compare/v0.5.0...v0.6.0
163
190
  [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/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 });
@@ -78,7 +82,8 @@ const DIVISIONS = {
78
82
  "per-pale": (a, b) => layer(`linear-gradient(to right, ${a} 50%, ${b} 50%)`),
79
83
 
80
84
  // 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})`),
85
+ "per-fess": (a, b) =>
86
+ layer(`linear-gradient(to bottom, ${a} ${FESS_POINT}%, ${b} ${FESS_POINT}%)`),
82
87
 
83
88
  // Line from dexter chief to sinister base: first in the upper half.
84
89
  // With a corner-to-corner gradient, the 50% line joins the other two corners.
@@ -106,9 +111,11 @@ const DIVISIONS = {
106
111
  ].join(", "),
107
112
  },
108
113
 
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.
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.
112
119
  "per-saltire": (a, b) => ({
113
120
  "background-image": [
114
121
  `linear-gradient(to bottom left, ${a} 50%, ${b} 50%)`,
@@ -116,10 +123,24 @@ const DIVISIONS = {
116
123
  `linear-gradient(to bottom left, ${b} 50%, ${a} 50%)`,
117
124
  `linear-gradient(to bottom right, ${b} 50%, ${a} 50%)`,
118
125
  `linear-gradient(${a}, ${a})`,
126
+ `linear-gradient(${a}, ${a})`,
119
127
  `linear-gradient(${b}, ${b})`,
120
128
  ].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",
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}%`,
143
+ ].join(", "),
123
144
  "background-repeat": "no-repeat",
124
145
  }),
125
146
 
@@ -128,14 +149,15 @@ const DIVISIONS = {
128
149
  // With a fur, the bands can't overlap, or the fur's transparent half shows the band below.
129
150
  quarterly: (a, b, fur) => ({
130
151
  "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%"}`,
152
+ "background-size": `100% ${FESS_POINT}%, 100% ${fur ? `calc(100% - ${FESS_POINT}%)` : "100%"}`,
132
153
  "background-position": fur ? "top, bottom" : "top, top",
133
154
  "background-repeat": "no-repeat",
134
155
  }),
135
156
 
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.
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.
139
161
  gyronny: (a, b) => ({
140
162
  "background-color": b,
141
163
  "background-image": [
@@ -143,9 +165,19 @@ const DIVISIONS = {
143
165
  `linear-gradient(to bottom right, ${b} 50%, ${a} 50%)`,
144
166
  `linear-gradient(to bottom left, ${b} 50%, ${a} 50%)`,
145
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",
146
180
  ].join(", "),
147
- "background-size": "50% 50%",
148
- "background-position": "left top, right top, right bottom, left bottom",
149
181
  "background-repeat": "no-repeat",
150
182
  }),
151
183