grailwind 0.8.1 → 0.9.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 +46 -1
- package/README.md +13 -8
- package/charges.js +36 -13
- package/data.js +95 -14
- package/divisions.js +48 -12
- package/furs.js +181 -23
- package/grailwind.css +178 -30
- package/ordinaries.js +452 -17
- package/package.json +2 -2
- package/paths.js +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,50 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.0] - 2026-11-15
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Engrailed and invected bordures, e.g. `bordure-engrailed-or`. On a frame the field grows into the
|
|
14
|
+
bites of an engrailed one; off a frame they are plain.
|
|
15
|
+
- Bordures in a fur, e.g. `bordure-ermine` and `bordure-engrailed-vair`. Off a shield's field they
|
|
16
|
+
show the fur's ground.
|
|
17
|
+
- A bordure on a per-pale part follows the shield's outline and stops at the line of
|
|
18
|
+
impalement. A chief on that part goes under the bordure.
|
|
19
|
+
- Vair and potent, e.g. `vair`, `counter-vair`, `potent` and `vairy-or-gules`.
|
|
20
|
+
- `fur-<n>` sets how many tiles of a fur go across, e.g. `fur-3` for beffroi.
|
|
21
|
+
- More charge counts: 7, 9 and 10 on a plain field; 4 on a cross or saltire, 2 and 5 on a chevron
|
|
22
|
+
and 4 and 5 on a pale, fess or bend with `on`; 3 round a bend and 4, 9 and 10 round a chevron
|
|
23
|
+
with `between`.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- The bordure is an eighth of the shield's width, so the field inside it is smaller.
|
|
28
|
+
- **Breaking:** the shield is a frame round its field. `escutcheon` is now
|
|
29
|
+
`heater`, with the blazon on its one child:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<!-- before -->
|
|
33
|
+
<div class="escutcheon azure fess-or"></div>
|
|
34
|
+
<!-- after -->
|
|
35
|
+
<div class="heater"><div class="azure fess-or"></div></div>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The heater is pointed, after Fox-Davies, and its outline is drawn just
|
|
39
|
+
inside its edge. An inescutcheon is a frame too, round its own field, and
|
|
40
|
+
pointed. A bordure on a shield's field follows its outline and scales with
|
|
41
|
+
it; on a part of a divided field or a canton, it's still a border.
|
|
42
|
+
|
|
43
|
+
- `@grailwind/eslint-plugin` 0.1.0-beta.5 needs `grailwind` 0.9.x. The vair
|
|
44
|
+
family isn't checked by the rule of tincture unless the `furs` option names
|
|
45
|
+
it. The new `field-in-shield` rule flags a blazon on the frame, and
|
|
46
|
+
`escutcheon`.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- A canton, inescutcheon or `charges` over all of a divided field with parts no longer takes a
|
|
51
|
+
part's place.
|
|
52
|
+
|
|
9
53
|
## [0.8.1] - 2026-11-13
|
|
10
54
|
|
|
11
55
|
### Fixed
|
|
@@ -193,7 +237,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
193
237
|
Initial release: tinctures, divisions, ordinaries, charges, strict mode and
|
|
194
238
|
custom SVG charges.
|
|
195
239
|
|
|
196
|
-
[Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.
|
|
240
|
+
[Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.9.0...HEAD
|
|
241
|
+
[0.9.0]: https://github.com/pafirmin/grailwind/compare/v0.8.1...v0.9.0
|
|
197
242
|
[0.8.1]: https://github.com/pafirmin/grailwind/compare/v0.8.0...v0.8.1
|
|
198
243
|
[0.8.0]: https://github.com/pafirmin/grailwind/compare/v0.7.0...v0.8.0
|
|
199
244
|
[0.7.0]: https://github.com/pafirmin/grailwind/compare/v0.6.0...v0.7.0
|
package/README.md
CHANGED
|
@@ -11,11 +11,13 @@ Heraldic blazon for [Tailwind CSS](https://tailwindcss.com). Draw coats of
|
|
|
11
11
|
arms, or style anything else, in the language heralds have used for 800 years:
|
|
12
12
|
|
|
13
13
|
```html
|
|
14
|
-
<div class="
|
|
15
|
-
<div class="
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
<div class="heater">
|
|
15
|
+
<div class="azure chevron-or">
|
|
16
|
+
<div class="charges between">
|
|
17
|
+
<i class="mullet argent"></i>
|
|
18
|
+
<i class="mullet argent"></i>
|
|
19
|
+
<i class="mullet argent"></i>
|
|
20
|
+
</div>
|
|
19
21
|
</div>
|
|
20
22
|
</div>
|
|
21
23
|
```
|
|
@@ -53,9 +55,12 @@ tinctures replace Tailwind's colours entirely.
|
|
|
53
55
|
|
|
54
56
|
- **Tinctures:** a tincture on its own sets the background (`azure`), and
|
|
55
57
|
Tailwind's prefixes work as usual (`text-or`). There are also the furs `ermine`,
|
|
56
|
-
`ermines`, `erminois` and `
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
`ermines`, `erminois`, `pean`, `vair` and `potent` (with `vairy-or-gules` and the like), which
|
|
59
|
+
work in divisions too, and `fur-<n>` sets their density.
|
|
60
|
+
- **The shield:** `heater`, a pointed shield round its one child, the field, plus `bordure-*` (a
|
|
61
|
+
eighth of the shield wide, also `bordure-engrailed-*` and `bordure-invected-*`), `inescutcheon` (a
|
|
62
|
+
small shield at the centre) and `canton` (a ninth of the shield in dexter chief), each with its
|
|
63
|
+
own field and charges.
|
|
59
64
|
- **Divided fields:** `per-pale-or-gules`, `quarterly-azure-argent`,
|
|
60
65
|
`gyronny-or-sable`, `barry-argent-azure`, `bendy-argent-gules`, `chequy-or-azure` and more, or
|
|
61
66
|
`per-pale`, `per-fess` and `quarterly` for layout.
|
package/charges.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Grailwind CSS: charges and their arrangement.
|
|
3
3
|
*/
|
|
4
4
|
import plugin from "tailwindcss/plugin";
|
|
5
|
-
import { POINTS, ROUNDELS } from "./data.js";
|
|
5
|
+
import { BETWEEN, BETWEEN_LINED, ON, POINTS, ROUNDELS } from "./data.js";
|
|
6
6
|
import { AT } from "./furs.js";
|
|
7
7
|
|
|
8
8
|
const star = (cx, cy, r, points = 5) => {
|
|
@@ -67,9 +67,12 @@ const DEFAULTS = {
|
|
|
67
67
|
1: { size: 50, points: [[50, 45]] },
|
|
68
68
|
2: { size: 30, points: [[50, 24], [50, 66]] },
|
|
69
69
|
3: { size: 28, points: [[28, 25], [72, 25], [50, 64]] },
|
|
70
|
-
4: { size: 24, points: [[30, 24], [70, 24], [
|
|
71
|
-
5: { size: 22, points: [[27, 20], [73, 20], [50, 42], [
|
|
70
|
+
4: { size: 24, points: [[30, 24], [70, 24], [30, 60], [70, 60]] },
|
|
71
|
+
5: { size: 22, points: [[27, 20], [73, 20], [50, 42], [27, 64], [73, 64]] },
|
|
72
72
|
6: { size: 20, points: [[20, 18], [50, 18], [80, 18], [35, 42], [65, 42], [50, 66]] },
|
|
73
|
+
7: { size: 20, points: [[24, 18], [50, 18], [76, 18], [24, 42], [50, 42], [76, 42], [50, 66]] },
|
|
74
|
+
9: { size: 17, points: [[22, 16], [50, 16], [78, 16], [24, 38], [50, 38], [76, 38], [32, 60], [50, 60], [68, 60]] },
|
|
75
|
+
10: { size: 15, points: [[17, 15], [39, 15], [61, 15], [83, 15], [28, 35], [50, 35], [72, 35], [39, 55], [61, 55], [50, 75]] },
|
|
73
76
|
};
|
|
74
77
|
|
|
75
78
|
const POINT_SIZE = 22;
|
|
@@ -155,15 +158,16 @@ const inheritOnCounterchange = Object.fromEntries(
|
|
|
155
158
|
COUNTERCHANGE.map((n) => [`--on-counterchange-${n}`, "inherit"]),
|
|
156
159
|
);
|
|
157
160
|
|
|
158
|
-
// A layer the size of the
|
|
159
|
-
// for fixed positioning
|
|
161
|
+
// A layer the size of the field, as the heater (or a part of a divided shield, or a canton) is
|
|
162
|
+
// the containing block for fixed positioning, and the field lies inside its bordure by
|
|
163
|
+
// --layer-inset. The charge's mask cuts it to shape.
|
|
160
164
|
const counterchanged = {
|
|
161
165
|
...inheritCounterchange,
|
|
162
166
|
...inheritOnCounterchange,
|
|
163
167
|
"&::before": {
|
|
164
168
|
content: '""',
|
|
165
169
|
position: "fixed",
|
|
166
|
-
inset: "
|
|
170
|
+
inset: "var(--layer-inset, 0px)",
|
|
167
171
|
...inheritCounterchange,
|
|
168
172
|
...inheritOnCounterchange,
|
|
169
173
|
...Object.fromEntries(
|
|
@@ -187,6 +191,15 @@ const atPoint = (name) => {
|
|
|
187
191
|
: place([left, top], POINT_SIZE);
|
|
188
192
|
};
|
|
189
193
|
|
|
194
|
+
/**
|
|
195
|
+
* @param {object[]} layouts Entries of `ON`, `BETWEEN` or `BETWEEN_LINED`, with optional `counts`.
|
|
196
|
+
* @returns {number[]} Every number of charges some layout places, ascending.
|
|
197
|
+
*/
|
|
198
|
+
const countKeys = (layouts) =>
|
|
199
|
+
[...new Set(layouts.flatMap((layout) => Object.keys(layout.counts ?? {}).map(Number)))].sort(
|
|
200
|
+
(a, b) => a - b,
|
|
201
|
+
);
|
|
202
|
+
|
|
190
203
|
const hasCount = (n) => `:has(> :nth-child(${n}):last-child)`;
|
|
191
204
|
|
|
192
205
|
export default plugin(({ addUtilities, matchUtilities }) => {
|
|
@@ -211,7 +224,7 @@ export default plugin(({ addUtilities, matchUtilities }) => {
|
|
|
211
224
|
const charges = {
|
|
212
225
|
position: "absolute",
|
|
213
226
|
inset: "var(--field-top, 0%) 0 0",
|
|
214
|
-
// Above the
|
|
227
|
+
// Above the field's ordinaries. Charges on the chief go higher, with it (see below).
|
|
215
228
|
"z-index": "1",
|
|
216
229
|
...inheritCounterchange,
|
|
217
230
|
"& > *": {
|
|
@@ -241,8 +254,11 @@ export default plugin(({ addUtilities, matchUtilities }) => {
|
|
|
241
254
|
utilities[`.${name}`] = arrangement;
|
|
242
255
|
}
|
|
243
256
|
|
|
257
|
+
// Charges on the chief go over it (z 3), which goes over a frame's bordure ring (z 2).
|
|
258
|
+
const onChief = { "z-index": "4" };
|
|
259
|
+
|
|
244
260
|
for (const [name, counts] of Object.entries(ALONG)) {
|
|
245
|
-
const arrangement = {};
|
|
261
|
+
const arrangement = name === "in-chief" ? { "&.charges": onChief } : {};
|
|
246
262
|
for (const [count, points] of Object.entries(counts)) {
|
|
247
263
|
points.forEach((point, i) => {
|
|
248
264
|
arrangement[`&${hasCount(count)} > :nth-child(${i + 1})`] = atPoint(point);
|
|
@@ -260,8 +276,12 @@ export default plugin(({ addUtilities, matchUtilities }) => {
|
|
|
260
276
|
"--charge-width": "var(--charge-size, var(--between-size))",
|
|
261
277
|
};
|
|
262
278
|
}
|
|
263
|
-
// The counts some ordinary overrides in
|
|
264
|
-
|
|
279
|
+
// The counts some ordinary overrides in `counts` (see data.js).
|
|
280
|
+
const betweenCounts = countKeys([
|
|
281
|
+
...Object.values(BETWEEN),
|
|
282
|
+
...Object.values(BETWEEN_LINED).flatMap((lines) => Object.values(lines)),
|
|
283
|
+
]);
|
|
284
|
+
for (const count of betweenCounts) {
|
|
265
285
|
for (let i = 1; i <= count; i++) {
|
|
266
286
|
between[`&${hasCount(count)} > :nth-child(${i})`] = {
|
|
267
287
|
left: `var(--between-${count}-${i}-x, var(--between-${i}-x))`,
|
|
@@ -275,7 +295,7 @@ export default plugin(({ addUtilities, matchUtilities }) => {
|
|
|
275
295
|
// Falls back to the plain-field default when the ordinary has no points for this count.
|
|
276
296
|
// An undefined var() would otherwise unset `left`/`top` instead of deferring to the :where() default.
|
|
277
297
|
const on = {};
|
|
278
|
-
for (const count of
|
|
298
|
+
for (const count of countKeys(Object.values(ON))) {
|
|
279
299
|
const fallback = DEFAULTS[count];
|
|
280
300
|
for (let i = 1; i <= count; i++) {
|
|
281
301
|
const [left, top] = fallback?.points[i - 1] ?? [];
|
|
@@ -286,7 +306,7 @@ export default plugin(({ addUtilities, matchUtilities }) => {
|
|
|
286
306
|
on[`&${hasCount(count)} > :nth-child(${i})`] = {
|
|
287
307
|
left: at("x", left),
|
|
288
308
|
top: at("y", top),
|
|
289
|
-
"--charge-width": `var(--charge-size, var(--on-size, ${fallback?.size ?? 16}%))`,
|
|
309
|
+
"--charge-width": `var(--charge-size, var(--on-${count}-size, var(--on-size, ${fallback?.size ?? 16}%)))`,
|
|
290
310
|
};
|
|
291
311
|
}
|
|
292
312
|
}
|
|
@@ -294,7 +314,10 @@ export default plugin(({ addUtilities, matchUtilities }) => {
|
|
|
294
314
|
utilities[".on"] = { "&": inheritOnCounterchange, ...on };
|
|
295
315
|
|
|
296
316
|
for (const name of Object.keys(POINTS)) {
|
|
297
|
-
utilities[`.in-${name}`] = {
|
|
317
|
+
utilities[`.in-${name}`] = {
|
|
318
|
+
".charges > &": atPoint(name),
|
|
319
|
+
...(CHIEF_POINTS.has(name) && { ".charges:has(> &)": onChief }),
|
|
320
|
+
};
|
|
298
321
|
}
|
|
299
322
|
|
|
300
323
|
// The whole field, under the ordinaries and the chief. A custom charge has no tile, so it
|
package/data.js
CHANGED
|
@@ -31,6 +31,12 @@ export const DIVISIONS = [
|
|
|
31
31
|
"chequy",
|
|
32
32
|
];
|
|
33
33
|
|
|
34
|
+
// The classes that frame a field: a shape round one child, which carries the blazon.
|
|
35
|
+
export const FRAMES = ["heater", "inescutcheon"];
|
|
36
|
+
|
|
37
|
+
// The lines a bordure can have besides plain, e.g. `bordure-engrailed-or`.
|
|
38
|
+
export const BORDURE_LINES = ["engrailed", "invected"];
|
|
39
|
+
|
|
34
40
|
// Ordinaries that can be reversed, e.g. `pile-reversed-or`: the same shape upside down.
|
|
35
41
|
export const REVERSIBLE = ["chevron", "pall", "pile"];
|
|
36
42
|
const reversed = (ordinary) => `${ordinary}-reversed`;
|
|
@@ -100,12 +106,47 @@ export const VOIDABLE = [
|
|
|
100
106
|
...REVERSIBLE.map(reversed),
|
|
101
107
|
];
|
|
102
108
|
|
|
103
|
-
//
|
|
109
|
+
// The vair family: plain vair and potent are argent and azure, and the -y forms take a metal and a
|
|
110
|
+
// colour, metal first as blazon writes them.
|
|
111
|
+
const VAIRS = ["vair", "counter-vair", "potent", "counter-potent"];
|
|
112
|
+
const VAIRY = {
|
|
113
|
+
vair: "vairy",
|
|
114
|
+
"counter-vair": "counter-vairy",
|
|
115
|
+
potent: "potenty",
|
|
116
|
+
"counter-potent": "counter-potenty",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Each fur's pattern: ermine's spots, or the vair family's panes.
|
|
120
|
+
export const FUR_PATTERNS = {
|
|
121
|
+
ermine: "ermine",
|
|
122
|
+
ermines: "ermine",
|
|
123
|
+
erminois: "ermine",
|
|
124
|
+
pean: "ermine",
|
|
125
|
+
...Object.fromEntries(VAIRS.map((pattern) => [pattern, pattern])),
|
|
126
|
+
...Object.fromEntries(
|
|
127
|
+
VAIRS.flatMap((pattern) =>
|
|
128
|
+
METALS.flatMap((metal) =>
|
|
129
|
+
COLOURS.map((colour) => [`${VAIRY[pattern]}-${metal}-${colour}`, pattern]),
|
|
130
|
+
),
|
|
131
|
+
),
|
|
132
|
+
),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Each fur's [ground, drawn] tinctures: ermine's spots, or the vair family's panes that point up in
|
|
136
|
+
// the top row, which is the first tincture blazoned.
|
|
104
137
|
export const FURS = {
|
|
105
138
|
ermine: ["argent", "sable"],
|
|
106
139
|
ermines: ["sable", "argent"],
|
|
107
140
|
erminois: ["or", "sable"],
|
|
108
141
|
pean: ["sable", "or"],
|
|
142
|
+
...Object.fromEntries(VAIRS.map((pattern) => [pattern, ["azure", "argent"]])),
|
|
143
|
+
...Object.fromEntries(
|
|
144
|
+
VAIRS.flatMap((pattern) =>
|
|
145
|
+
METALS.flatMap((metal) =>
|
|
146
|
+
COLOURS.map((colour) => [`${VAIRY[pattern]}-${metal}-${colour}`, [colour, metal]]),
|
|
147
|
+
),
|
|
148
|
+
),
|
|
149
|
+
),
|
|
109
150
|
};
|
|
110
151
|
|
|
111
152
|
// The points of the shield, as [left%, top%], for placing a single charge, e.g. `in-dexter-chief`.
|
|
@@ -142,11 +183,16 @@ export const BETWEEN = {
|
|
|
142
183
|
2: { points: [[50, 15], [50, 78]] },
|
|
143
184
|
6: { size: 14, points: [[20, 14], [50, 14], [80, 14], [32, 70], [68, 70], [50, 86]] } } },
|
|
144
185
|
bend: { size: 24, points: [[70, 26], [30, 68]], counts: {
|
|
186
|
+
3: { size: 18, points: [[60, 13], [86, 36], [30, 68]] },
|
|
145
187
|
6: { size: 14, points: [[48, 9], [76, 12], [84, 38], [9, 48], [18, 72], [38, 84]] } } },
|
|
146
188
|
"bend-sinister": { size: 24, points: [[30, 26], [70, 68]], counts: {
|
|
189
|
+
3: { size: 18, points: [[40, 13], [14, 36], [70, 68]] },
|
|
147
190
|
6: { size: 14, points: [[52, 9], [24, 12], [16, 38], [91, 48], [82, 72], [62, 84]] } } },
|
|
148
191
|
chevron: { size: 22, points: [[24, 22], [76, 22], [50, 70]], counts: {
|
|
149
|
-
|
|
192
|
+
4: { size: 15, points: [[20, 16], [50, 9], [80, 16], [50, 74]] },
|
|
193
|
+
6: { size: 12, points: [[18, 14], [50, 11], [82, 14], [40, 77], [60, 77], [50, 89]] },
|
|
194
|
+
9: { size: 11, points: [[14, 12], [30, 12], [22, 26], [70, 12], [86, 12], [78, 26], [43, 73], [57, 73], [50, 86]] },
|
|
195
|
+
10: { size: 11, points: [[14, 12], [30, 12], [22, 26], [70, 12], [86, 12], [78, 26], [43, 71], [57, 71], [43, 85], [57, 85]] } } },
|
|
150
196
|
cross: { size: 18, points: [[20, 17], [80, 17], [24, 72], [76, 72]] },
|
|
151
197
|
saltire: { size: 16, points: [[50, 13], [14, 45], [86, 45], [50, 84]], counts: {
|
|
152
198
|
2: { points: [[14, 45], [86, 45]] } } },
|
|
@@ -158,18 +204,53 @@ export const BETWEEN = {
|
|
|
158
204
|
// `on` charges shrink on lines that cut deep into the ordinary, by line.
|
|
159
205
|
export const ON_SCALE = { engrailed: 0.8, indented: 0.9 };
|
|
160
206
|
|
|
161
|
-
// Where charges go for "on", along the ordinary's centre line, by number of charges.
|
|
207
|
+
// Where charges go for "on", along the ordinary's centre line, by number of charges. A count
|
|
208
|
+
// can set its own size, else the ordinary's applies.
|
|
162
209
|
// prettier-ignore
|
|
163
210
|
export const ON = {
|
|
164
|
-
pale: { size: 20, counts: {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
211
|
+
pale: { size: 20, counts: {
|
|
212
|
+
1: { points: [[50, 45]] },
|
|
213
|
+
2: { points: [[50, 28], [50, 62]] },
|
|
214
|
+
3: { points: [[50, 18], [50, 45], [50, 72]] },
|
|
215
|
+
4: { size: 18, points: [[50, 13], [50, 35], [50, 57], [50, 79]] },
|
|
216
|
+
5: { size: 15, points: [[50, 12], [50, 29], [50, 46], [50, 63], [50, 80]] } } },
|
|
217
|
+
fess: { size: 22, counts: {
|
|
218
|
+
1: { points: [[50, 45]] },
|
|
219
|
+
2: { points: [[30, 45], [70, 45]] },
|
|
220
|
+
3: { points: [[20, 45], [50, 45], [80, 45]] },
|
|
221
|
+
4: { size: 18, points: [[17, 45], [39, 45], [61, 45], [83, 45]] },
|
|
222
|
+
5: { size: 15, points: [[14, 45], [32, 45], [50, 45], [68, 45], [86, 45]] } } },
|
|
223
|
+
bend: { size: 18, counts: {
|
|
224
|
+
1: { points: [[50, 50]] },
|
|
225
|
+
2: { points: [[33, 33], [67, 67]] },
|
|
226
|
+
3: { points: [[24, 24], [50, 50], [76, 76]] },
|
|
227
|
+
4: { size: 16, points: [[22, 22], [40.7, 40.7], [59.3, 59.3], [78, 78]] },
|
|
228
|
+
5: { size: 12, points: [[17, 17], [32, 32], [47, 47], [62, 62], [77, 77]] } } },
|
|
229
|
+
"bend-sinister": { size: 18, counts: {
|
|
230
|
+
1: { points: [[50, 50]] },
|
|
231
|
+
2: { points: [[67, 33], [33, 67]] },
|
|
232
|
+
3: { points: [[76, 24], [50, 50], [24, 76]] },
|
|
233
|
+
4: { size: 16, points: [[78, 22], [59.3, 40.7], [40.7, 59.3], [22, 78]] },
|
|
234
|
+
5: { size: 12, points: [[83, 17], [68, 32], [53, 47], [38, 62], [23, 77]] } } },
|
|
235
|
+
chevron: { size: 15, counts: {
|
|
236
|
+
1: { points: [[50, 36]] },
|
|
237
|
+
2: { points: [[28, 57], [72, 57]] },
|
|
238
|
+
3: { points: [[20, 65], [50, 36], [80, 65]] },
|
|
239
|
+
5: { size: 13, points: [[50, 36], [35, 51], [65, 51], [20, 66], [80, 66]] } } },
|
|
240
|
+
cross: { size: 15, counts: {
|
|
241
|
+
1: { points: [[50, 45]] },
|
|
242
|
+
4: { points: [[50, 15], [18, 45], [82, 45], [50, 80]] },
|
|
243
|
+
5: { points: [[50, 15], [18, 45], [50, 45], [82, 45], [50, 80]] } } },
|
|
244
|
+
saltire: { size: 16, counts: {
|
|
245
|
+
1: { points: [[50, 45]] },
|
|
246
|
+
4: { points: [[22, 19.8], [78, 19.8], [22, 70.2], [78, 70.2]] },
|
|
247
|
+
5: { points: [[22, 19.8], [78, 19.8], [50, 45], [22, 70.2], [78, 70.2]] } } },
|
|
248
|
+
pall: { size: 16, counts: {
|
|
249
|
+
1: { points: [[50, 40]] },
|
|
250
|
+
3: { points: [[26, 20.8], [74, 20.8], [50, 75]] } } },
|
|
251
|
+
pile: { size: 14, counts: {
|
|
252
|
+
1: { points: [[50, 25]] },
|
|
253
|
+
3: { points: [[50, 14], [50, 34], [50, 54]] } } },
|
|
173
254
|
};
|
|
174
255
|
|
|
175
256
|
const mirror = (points) => points.map(([x, y]) => [x, 100 - y]);
|
|
@@ -185,7 +266,7 @@ for (const ordinary of REVERSIBLE) {
|
|
|
185
266
|
};
|
|
186
267
|
ON[reversed(ordinary)] = {
|
|
187
268
|
size: ON[ordinary].size,
|
|
188
|
-
counts: mapValues(ON[ordinary].counts, mirror),
|
|
269
|
+
counts: mapValues(ON[ordinary].counts, (count) => ({ ...count, points: mirror(count.points) })),
|
|
189
270
|
};
|
|
190
271
|
}
|
|
191
272
|
|
|
@@ -208,7 +289,7 @@ export const BETWEEN_LINED = {
|
|
|
208
289
|
indented: { points: [[70, 26], [24, 72]] },
|
|
209
290
|
},
|
|
210
291
|
chevron: {
|
|
211
|
-
embattled: { counts: { 6: { size: 10, points: [[16, 13], [50, 8], [84, 13], [40,
|
|
292
|
+
embattled: { counts: { 6: { size: 10, points: [[16, 13], [50, 8], [84, 13], [40, 77], [60, 77], [50, 89]] } } },
|
|
212
293
|
},
|
|
213
294
|
"chevron-reversed": {
|
|
214
295
|
embattled: { points: [[26, 76], [74, 76], [50, 24]] },
|
package/divisions.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import plugin from "tailwindcss/plugin";
|
|
5
5
|
import { FURS, TINCTURES } from "./data.js";
|
|
6
6
|
import { furLayers } from "./furs.js";
|
|
7
|
+
import { PATHS } from "./paths.js";
|
|
7
8
|
|
|
8
9
|
// The fess point, in % down the shield: horizontal lines of partition pass through it, as the
|
|
9
10
|
// cross and fess do.
|
|
@@ -26,16 +27,40 @@ const stripes = (to, pieces) => (a, b) => {
|
|
|
26
27
|
};
|
|
27
28
|
};
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Points along a drawn outline's curves and lines, densely, in fractions of its box.
|
|
32
|
+
* @param {Array[]} segments The drawing's segments, as in paths.js, in real units (100 × 120).
|
|
33
|
+
* @returns {number[][]} [x, y] points, each 0 to 1.
|
|
34
|
+
*/
|
|
35
|
+
const outlineFractions = (segments) => {
|
|
36
|
+
const points = [];
|
|
37
|
+
let from = [0, 0];
|
|
38
|
+
for (const [command, ...n] of segments) {
|
|
39
|
+
if (command === "M" || command === "L") {
|
|
40
|
+
from = [n[0], n[1]];
|
|
41
|
+
points.push(from);
|
|
42
|
+
} else if (command === "C") {
|
|
43
|
+
for (let i = 1; i <= 1000; i++) {
|
|
44
|
+
const t = i / 1000;
|
|
45
|
+
const [a, b, c, d] = [(1 - t) ** 3, 3 * (1 - t) ** 2 * t, 3 * (1 - t) * t ** 2, t ** 3];
|
|
46
|
+
points.push([
|
|
47
|
+
a * from[0] + b * n[0] + c * n[2] + d * n[4],
|
|
48
|
+
a * from[1] + b * n[1] + c * n[3] + d * n[5],
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
from = [n[4], n[5]];
|
|
52
|
+
} else if (command !== "Z") {
|
|
53
|
+
throw new Error(`bendy can't read ${command} in the heater's outline`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return points.map(([x, y]) => [x / 100, y / 120]);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// How much of the corner-to-corner extent the shield covers, as the heater's base cuts the far
|
|
60
|
+
// corner away. On the 5:6 shield, a point's distance along the extent is (x + y) / 2 in fractions
|
|
61
|
+
// of the width and height, so it runs from the chief corner the bands start in to where a band's
|
|
62
|
+
// line is tangent to the base (drawings/heater.svg).
|
|
63
|
+
const BENDY_VISIBLE = Math.max(...outlineFractions(PATHS.heater).map(([x, y]) => (x + y) / 2));
|
|
39
64
|
|
|
40
65
|
// Diagonal bands parallel to per bend, `pieces` equal ones across the visible part of the shield,
|
|
41
66
|
// the last running to the edge. Explicit stops, not a repeating gradient, so a canton's square
|
|
@@ -295,6 +320,17 @@ const field = (layers, color) =>
|
|
|
295
320
|
"--field",
|
|
296
321
|
);
|
|
297
322
|
|
|
323
|
+
/**
|
|
324
|
+
* A field's first tincture, and the shield's, for a bordure ring of the first (see grailwind.css).
|
|
325
|
+
* Only the heater sets it, as an inescutcheon's "of the first" is the shield's.
|
|
326
|
+
* @param {string} colour The first tincture's CSS colour.
|
|
327
|
+
* @returns {Record<string, unknown>} The declarations, with the heater's as a nested rule.
|
|
328
|
+
*/
|
|
329
|
+
const firstTincture = (colour) => ({
|
|
330
|
+
"--field-first": colour,
|
|
331
|
+
".heater:has(> &)": { "--frame-first": colour },
|
|
332
|
+
});
|
|
333
|
+
|
|
298
334
|
// The field with its tinctures swapped, for counterchanged ordinaries to paint.
|
|
299
335
|
const counterchange = (rules) =>
|
|
300
336
|
Object.fromEntries(
|
|
@@ -321,7 +357,7 @@ export default plugin(({ addUtilities, theme }) => {
|
|
|
321
357
|
...unswapped,
|
|
322
358
|
...field(toLayers(unswapped), unswapped["background-color"]),
|
|
323
359
|
...counterchange(background(b, a)),
|
|
324
|
-
|
|
360
|
+
...firstTincture(a),
|
|
325
361
|
};
|
|
326
362
|
}
|
|
327
363
|
}
|
|
@@ -341,7 +377,7 @@ export default plugin(({ addUtilities, theme }) => {
|
|
|
341
377
|
...joinLayers(layers, "background"),
|
|
342
378
|
...field(layers),
|
|
343
379
|
...counterchange(withFur(division, second, first, theme)),
|
|
344
|
-
...(!FURS[first] &&
|
|
380
|
+
...(!FURS[first] && firstTincture(`var(--color-${first})`)),
|
|
345
381
|
};
|
|
346
382
|
}
|
|
347
383
|
}
|