grailwind 0.4.0 → 0.5.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 +44 -1
- package/README.md +13 -6
- package/charges.js +50 -11
- package/data.js +40 -10
- package/divisions.js +36 -0
- package/grailwind.css +79 -2
- package/ordinaries.js +141 -45
- package/package.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,48 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2026-11-04
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `in-chief` and `in-base`: one to three charges along the chief or base. On a
|
|
14
|
+
chief, they sit on it. The ESLint plugin reports an arrangement put on a
|
|
15
|
+
charge rather than its container.
|
|
16
|
+
- `-of-the-first`: the field's first tincture, as in a blazon, e.g.
|
|
17
|
+
`pale-of-the-first`. The ESLint plugin checks it.
|
|
18
|
+
- Striped fields: `barry-*` and `paly-*` (of six), `barry-of-eight-*`,
|
|
19
|
+
`barry-of-ten-*` and `paly-of-eight-*`. The first tincture is in chief or
|
|
20
|
+
on the dexter. They take furs and counterchanged, like the other divisions.
|
|
21
|
+
- `per-saltire-*`: a field divided on both diagonals, the first tincture in
|
|
22
|
+
chief and base.
|
|
23
|
+
- Bars, named by count: `bar-*`, `two-bars-*` and `three-bars-*`. They take
|
|
24
|
+
lines, furs, fimbriation and counterchanged, like the fess.
|
|
25
|
+
- `pile-*`: a wedge from the chief to a point. It takes lines, furs,
|
|
26
|
+
fimbriation and counterchanged, one or three charges `on` it, and two
|
|
27
|
+
`between`.
|
|
28
|
+
- `canton`: a ninth of the shield in dexter chief, over every ordinary and
|
|
29
|
+
charge, with its own field, divisions, ordinaries and charges, like the
|
|
30
|
+
inescutcheon.
|
|
31
|
+
- Six charges `between` a fess, bend, bend sinister, chevron or pale.
|
|
32
|
+
- `inescutcheon`: a small shield at the centre of the arms, over everything
|
|
33
|
+
else, with its own field, ordinaries and charges. A bordure on it is a third
|
|
34
|
+
as wide.
|
|
35
|
+
- The ESLint plugin checks a plain canton or inescutcheon against the field it
|
|
36
|
+
lies on. One with anything on it counts as arms of its own.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- The `in-chief` and `in-base` points are now `in-chief-point` and
|
|
41
|
+
`in-base-point`, like the honour, fess and nombril points.
|
|
42
|
+
- The canton and the chief surmount a bordure, as in heraldry. The canton is
|
|
43
|
+
still a third of the whole shield, and the chief's lower edge doesn't move.
|
|
44
|
+
- `@grailwind/eslint-plugin` 0.1.0-beta.1 needs `grailwind` 0.5.x, for the
|
|
45
|
+
renamed points.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Two charges `between` a fess, saltire or pall are placed one each side.
|
|
50
|
+
|
|
9
51
|
## [0.4.0] - 2026-11-03
|
|
10
52
|
|
|
11
53
|
### Added
|
|
@@ -71,7 +113,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
71
113
|
Initial release: tinctures, divisions, ordinaries, charges, strict mode and
|
|
72
114
|
custom SVG charges.
|
|
73
115
|
|
|
74
|
-
[Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.
|
|
116
|
+
[Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.5.0...HEAD
|
|
117
|
+
[0.5.0]: https://github.com/pafirmin/grailwind/compare/v0.4.0...v0.5.0
|
|
75
118
|
[0.4.0]: https://github.com/pafirmin/grailwind/compare/v0.3.0...v0.4.0
|
|
76
119
|
[0.3.0]: https://github.com/pafirmin/grailwind/compare/v0.2.0...v0.3.0
|
|
77
120
|
[0.2.0]: https://github.com/pafirmin/grailwind/compare/v0.1.0...v0.2.0
|
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Grailwind CSS
|
|
2
2
|
|
|
3
|
+
[](https://github.com/pafirmin/grailwind/actions/workflows/test.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/grailwind)
|
|
5
|
+
[](https://www.npmjs.com/package/@grailwind/eslint-plugin)
|
|
6
|
+
[](https://github.com/pafirmin/grailwind/blob/main/LICENSE)
|
|
7
|
+
|
|
3
8
|

|
|
4
9
|
|
|
5
10
|
Heraldic blazon for [Tailwind CSS](https://tailwindcss.com). Draw coats of
|
|
@@ -49,14 +54,16 @@ tinctures replace Tailwind's colours entirely.
|
|
|
49
54
|
- **Tinctures:** a tincture on its own sets the background (`azure`), and
|
|
50
55
|
Tailwind's prefixes work as usual (`text-or`). There are also the furs `ermine`,
|
|
51
56
|
`ermines`, `erminois` and `pean`, which work in divisions too.
|
|
52
|
-
- **The shield:** `escutcheon`, plus `bordure
|
|
57
|
+
- **The shield:** `escutcheon`, plus `bordure-*`, `inescutcheon` (a small shield at the centre)
|
|
58
|
+
and `canton` (a ninth of the shield in dexter chief), each with its own field and charges.
|
|
53
59
|
- **Divided fields:** `per-pale-or-gules`, `quarterly-azure-argent`,
|
|
54
|
-
`gyronny-or-sable` and more, or `per-pale`, `per-fess` and `quarterly` for
|
|
60
|
+
`gyronny-or-sable`, `barry-argent-azure` and more, or `per-pale`, `per-fess` and `quarterly` for
|
|
55
61
|
layout.
|
|
56
|
-
- **Ordinaries:** `fess-or`, `chevron-gules`, `cross-argent`, `chief-azure
|
|
57
|
-
and more, with `fimbriated-*` for a narrow border,
|
|
58
|
-
the like for an engrailed or invected edge,
|
|
59
|
-
fur, or `chevron-counterchanged` and the like
|
|
62
|
+
- **Ordinaries:** `fess-or`, `chevron-gules`, `cross-argent`, `chief-azure`,
|
|
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,
|
|
65
|
+
`chief-ermine` and the like in a fur, or `chevron-counterchanged` and the like
|
|
66
|
+
on a divided field.
|
|
60
67
|
- **Charges:** `mullet`, `fleur-de-lis`, `bezant` and more, arranged by
|
|
61
68
|
number, `in-pale`, `in-fess`, `in-bend`, `in-bend-sinister`, `on` or `between`
|
|
62
69
|
the ordinary, or placed at a named point such as `in-dexter-chief`, sized
|
package/charges.js
CHANGED
|
@@ -46,7 +46,22 @@ const DEFAULTS = {
|
|
|
46
46
|
const POINT_SIZE = 22;
|
|
47
47
|
|
|
48
48
|
// With a chief on the shield, these points sit on it (see ordinaries.js).
|
|
49
|
-
const CHIEF_POINTS = new Set(["dexter-chief", "chief", "sinister-chief"]);
|
|
49
|
+
const CHIEF_POINTS = new Set(["dexter-chief", "chief-point", "sinister-chief"]);
|
|
50
|
+
|
|
51
|
+
// Arrangements along the chief or base, by the points each count of charges takes.
|
|
52
|
+
// prettier-ignore
|
|
53
|
+
const ALONG = {
|
|
54
|
+
"in-chief": {
|
|
55
|
+
1: ["chief-point"],
|
|
56
|
+
2: ["dexter-chief", "sinister-chief"],
|
|
57
|
+
3: ["dexter-chief", "chief-point", "sinister-chief"],
|
|
58
|
+
},
|
|
59
|
+
"in-base": {
|
|
60
|
+
1: ["base-point"],
|
|
61
|
+
2: ["dexter-base", "sinister-base"],
|
|
62
|
+
3: ["dexter-base", "base-point", "sinister-base"],
|
|
63
|
+
},
|
|
64
|
+
};
|
|
50
65
|
|
|
51
66
|
// prettier-ignore
|
|
52
67
|
const ARRANGEMENTS = {
|
|
@@ -107,6 +122,18 @@ const counterchanged = {
|
|
|
107
122
|
},
|
|
108
123
|
};
|
|
109
124
|
|
|
125
|
+
// A charge at one of the shield's points. The chief's points sit on a chief when there is one.
|
|
126
|
+
const atPoint = (name) => {
|
|
127
|
+
const [left, top] = POINTS[name];
|
|
128
|
+
return CHIEF_POINTS.has(name)
|
|
129
|
+
? {
|
|
130
|
+
left: `${left}%`,
|
|
131
|
+
top: `var(--chief-points-y, ${top}%)`,
|
|
132
|
+
"--charge-width": `var(--charge-size, var(--chief-points-size, ${POINT_SIZE}%))`,
|
|
133
|
+
}
|
|
134
|
+
: place([left, top], POINT_SIZE);
|
|
135
|
+
};
|
|
136
|
+
|
|
110
137
|
const hasCount = (n) => `:has(> :nth-child(${n}):last-child)`;
|
|
111
138
|
|
|
112
139
|
export default plugin(({ addUtilities, matchUtilities }) => {
|
|
@@ -160,6 +187,16 @@ export default plugin(({ addUtilities, matchUtilities }) => {
|
|
|
160
187
|
utilities[`.${name}`] = arrangement;
|
|
161
188
|
}
|
|
162
189
|
|
|
190
|
+
for (const [name, counts] of Object.entries(ALONG)) {
|
|
191
|
+
const arrangement = {};
|
|
192
|
+
for (const [count, points] of Object.entries(counts)) {
|
|
193
|
+
points.forEach((point, i) => {
|
|
194
|
+
arrangement[`&${hasCount(count)} > :nth-child(${i + 1})`] = atPoint(point);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
utilities[`.${name}`] = arrangement;
|
|
198
|
+
}
|
|
199
|
+
|
|
163
200
|
// Positions come from the ordinary on the shield (see ordinaries.js).
|
|
164
201
|
const between = {};
|
|
165
202
|
for (let i = 1; i <= 4; i++) {
|
|
@@ -169,6 +206,16 @@ export default plugin(({ addUtilities, matchUtilities }) => {
|
|
|
169
206
|
"--charge-width": "var(--charge-size, var(--between-size))",
|
|
170
207
|
};
|
|
171
208
|
}
|
|
209
|
+
// The counts some ordinary overrides in BETWEEN's `counts` (see data.js).
|
|
210
|
+
for (const count of [2, 6]) {
|
|
211
|
+
for (let i = 1; i <= count; i++) {
|
|
212
|
+
between[`&${hasCount(count)} > :nth-child(${i})`] = {
|
|
213
|
+
left: `var(--between-${count}-${i}-x, var(--between-${i}-x))`,
|
|
214
|
+
top: `var(--between-${count}-${i}-y, var(--between-${i}-y))`,
|
|
215
|
+
"--charge-width": `var(--charge-size, var(--between-${count}-size, var(--between-size)))`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
172
219
|
utilities[".between"] = between;
|
|
173
220
|
|
|
174
221
|
// Falls back to the plain-field default when the ordinary has no points for this count.
|
|
@@ -191,16 +238,8 @@ export default plugin(({ addUtilities, matchUtilities }) => {
|
|
|
191
238
|
}
|
|
192
239
|
utilities[".on"] = on;
|
|
193
240
|
|
|
194
|
-
for (const
|
|
195
|
-
utilities[`.in-${name}`] = {
|
|
196
|
-
".charges > &": CHIEF_POINTS.has(name)
|
|
197
|
-
? {
|
|
198
|
-
left: `${left}%`,
|
|
199
|
-
top: `var(--chief-points-y, ${top}%)`,
|
|
200
|
-
"--charge-width": `var(--charge-size, var(--chief-points-size, ${POINT_SIZE}%))`,
|
|
201
|
-
}
|
|
202
|
-
: place([left, top], POINT_SIZE),
|
|
203
|
-
};
|
|
241
|
+
for (const name of Object.keys(POINTS)) {
|
|
242
|
+
utilities[`.in-${name}`] = { ".charges > &": atPoint(name) };
|
|
204
243
|
}
|
|
205
244
|
|
|
206
245
|
// On a charge, or on `charges` for all of them.
|
package/data.js
CHANGED
|
@@ -5,19 +5,30 @@
|
|
|
5
5
|
export const METALS = ["or", "argent"];
|
|
6
6
|
export const COLOURS = ["gules", "azure", "vert", "purpure", "sable"];
|
|
7
7
|
export const TINCTURES = [...METALS, ...COLOURS];
|
|
8
|
+
// Paints in the field's first tincture, as "of the first" in a blazon.
|
|
9
|
+
export const OF_THE_FIRST = "of-the-first";
|
|
8
10
|
|
|
9
11
|
export const DIVISIONS = [
|
|
10
12
|
"per-pale",
|
|
11
13
|
"per-fess",
|
|
12
14
|
"per-bend",
|
|
13
15
|
"per-bend-sinister",
|
|
16
|
+
"per-saltire",
|
|
14
17
|
"quarterly",
|
|
15
18
|
"gyronny",
|
|
19
|
+
"barry",
|
|
20
|
+
"barry-of-eight",
|
|
21
|
+
"barry-of-ten",
|
|
22
|
+
"paly",
|
|
23
|
+
"paly-of-eight",
|
|
16
24
|
];
|
|
17
25
|
|
|
18
26
|
export const ORDINARIES = [
|
|
19
27
|
"pale",
|
|
20
28
|
"fess",
|
|
29
|
+
"bar",
|
|
30
|
+
"two-bars",
|
|
31
|
+
"three-bars",
|
|
21
32
|
"bend",
|
|
22
33
|
"bend-sinister",
|
|
23
34
|
"chevron",
|
|
@@ -25,6 +36,7 @@ export const ORDINARIES = [
|
|
|
25
36
|
"saltire",
|
|
26
37
|
"pall",
|
|
27
38
|
"chief",
|
|
39
|
+
"pile",
|
|
28
40
|
];
|
|
29
41
|
|
|
30
42
|
export const CHARGES = [
|
|
@@ -60,30 +72,47 @@ export const FURS = {
|
|
|
60
72
|
// The points of the shield, as [left%, top%], for placing a single charge, e.g. `in-dexter-chief`.
|
|
61
73
|
export const POINTS = {
|
|
62
74
|
"dexter-chief": [25, 17],
|
|
63
|
-
chief: [50, 15],
|
|
75
|
+
"chief-point": [50, 15],
|
|
64
76
|
"sinister-chief": [75, 17],
|
|
65
77
|
"honour-point": [50, 30],
|
|
66
78
|
"fess-point": [50, 45],
|
|
67
79
|
"nombril-point": [50, 60],
|
|
68
80
|
"dexter-base": [30, 72],
|
|
69
|
-
base: [50, 84],
|
|
81
|
+
"base-point": [50, 84],
|
|
70
82
|
"sinister-base": [70, 72],
|
|
71
83
|
};
|
|
72
84
|
|
|
73
85
|
// Plain-field arrangements of a `charges` container, besides `between` and `on`.
|
|
74
|
-
export const ARRANGEMENTS = [
|
|
86
|
+
export const ARRANGEMENTS = [
|
|
87
|
+
"in-pale",
|
|
88
|
+
"in-fess",
|
|
89
|
+
"in-bend",
|
|
90
|
+
"in-bend-sinister",
|
|
91
|
+
"in-chief",
|
|
92
|
+
"in-base",
|
|
93
|
+
];
|
|
75
94
|
|
|
76
95
|
// Where charges go for "between", as [left%, top%], plus their size as % of width.
|
|
96
|
+
// `counts` overrides the points (and optionally the size) for one number of charges.
|
|
77
97
|
// prettier-ignore
|
|
78
98
|
export const BETWEEN = {
|
|
79
|
-
pale: { size: 18, points: [[18, 42], [82, 42]]
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
pale: { size: 18, points: [[18, 42], [82, 42]], counts: {
|
|
100
|
+
6: { size: 14, points: [[18, 18], [18, 45], [18, 70], [82, 18], [82, 45], [82, 70]] } } },
|
|
101
|
+
fess: { size: 20, points: [[30, 15], [70, 15], [50, 78]], counts: {
|
|
102
|
+
2: { points: [[50, 15], [50, 78]] },
|
|
103
|
+
6: { size: 14, points: [[20, 14], [50, 14], [80, 14], [32, 70], [68, 70], [50, 86]] } } },
|
|
104
|
+
bend: { size: 24, points: [[70, 26], [30, 68]], counts: {
|
|
105
|
+
6: { size: 14, points: [[48, 9], [76, 12], [84, 38], [9, 48], [18, 72], [38, 84]] } } },
|
|
106
|
+
"bend-sinister": { size: 24, points: [[30, 26], [70, 68]], counts: {
|
|
107
|
+
6: { size: 14, points: [[52, 9], [24, 12], [16, 38], [91, 48], [82, 72], [62, 84]] } } },
|
|
108
|
+
chevron: { size: 22, points: [[24, 22], [76, 22], [50, 70]], counts: {
|
|
109
|
+
6: { size: 12, points: [[18, 14], [50, 11], [82, 14], [38, 66], [62, 66], [50, 84]] } } },
|
|
84
110
|
cross: { size: 18, points: [[20, 17], [80, 17], [24, 72], [76, 72]] },
|
|
85
|
-
saltire: { size: 16, points: [[50, 13], [14, 50], [86, 50], [50, 84]]
|
|
86
|
-
|
|
111
|
+
saltire: { size: 16, points: [[50, 13], [14, 50], [86, 50], [50, 84]], counts: {
|
|
112
|
+
2: { points: [[14, 50], [86, 50]] } } },
|
|
113
|
+
pall: { size: 18, points: [[50, 11], [18, 56], [82, 56]], counts: {
|
|
114
|
+
2: { points: [[18, 56], [82, 56]] } } },
|
|
115
|
+
pile: { size: 20, points: [[18, 55], [82, 55]] },
|
|
87
116
|
};
|
|
88
117
|
|
|
89
118
|
// Where charges go for "on", along the ordinary's centre line, by number of charges.
|
|
@@ -97,4 +126,5 @@ export const ON = {
|
|
|
97
126
|
cross: { size: 15, counts: { 1: [[50, 45]], 5: [[50, 15], [18, 45], [50, 45], [82, 45], [50, 80]] } },
|
|
98
127
|
saltire: { size: 14, counts: { 1: [[50, 50]], 5: [[22, 22], [78, 22], [50, 50], [22, 78], [78, 78]] } },
|
|
99
128
|
pall: { size: 14, counts: { 1: [[50, 50]], 3: [[22, 22], [78, 22], [50, 75]] } },
|
|
129
|
+
pile: { size: 14, counts: { 1: [[50, 25]], 3: [[50, 14], [50, 34], [50, 54]] } },
|
|
100
130
|
};
|
package/divisions.js
CHANGED
|
@@ -11,6 +11,17 @@ const FESS_POINT = "45%";
|
|
|
11
11
|
// Each division maps (first, second) tincture colours to background rules.
|
|
12
12
|
const layer = (image) => ({ "background-image": image });
|
|
13
13
|
|
|
14
|
+
// Equal stripes, the first tincture at the top (barry) or on the dexter (paly): one pair, tiled.
|
|
15
|
+
// The repeat must be explicit, as toLayers defaults to no-repeat for the fur variants.
|
|
16
|
+
const stripes = (to, pieces) => (a, b) => {
|
|
17
|
+
const pair = `calc(200% / ${pieces})`;
|
|
18
|
+
return {
|
|
19
|
+
"background-image": `linear-gradient(to ${to}, ${a} 50%, ${b} 50%)`,
|
|
20
|
+
"background-size": to === "bottom" ? `100% ${pair}` : `${pair} 100%`,
|
|
21
|
+
"background-repeat": to === "bottom" ? "repeat-y" : "repeat-x",
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
14
25
|
const DIVISIONS = {
|
|
15
26
|
// Vertical split: first on the dexter (viewer's left).
|
|
16
27
|
"per-pale": (a, b) => layer(`linear-gradient(to right, ${a} 50%, ${b} 50%)`),
|
|
@@ -25,6 +36,21 @@ const DIVISIONS = {
|
|
|
25
36
|
// Line from sinister chief to dexter base: first in the upper half.
|
|
26
37
|
"per-bend-sinister": (a, b) => layer(`linear-gradient(to bottom right, ${a} 50%, ${b} 50%)`),
|
|
27
38
|
|
|
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.
|
|
41
|
+
"per-saltire": (a, b) => ({
|
|
42
|
+
"background-color": b,
|
|
43
|
+
"background-image": [
|
|
44
|
+
`linear-gradient(to bottom left, ${a} 50%, ${b} 50%)`,
|
|
45
|
+
`linear-gradient(to bottom right, ${a} 50%, ${b} 50%)`,
|
|
46
|
+
`linear-gradient(to bottom left, ${b} 50%, ${a} 50%)`,
|
|
47
|
+
`linear-gradient(to bottom right, ${b} 50%, ${a} 50%)`,
|
|
48
|
+
].join(", "),
|
|
49
|
+
"background-size": "50% 50%",
|
|
50
|
+
"background-position": "left top, right top, right bottom, left bottom",
|
|
51
|
+
"background-repeat": "no-repeat",
|
|
52
|
+
}),
|
|
53
|
+
|
|
28
54
|
// Quarters 1 and 4 (dexter chief, sinister base) are the first tincture.
|
|
29
55
|
// Linear gradients rather than conic, which render jagged edges in some browsers.
|
|
30
56
|
// With a fur, the bands can't overlap, or the fur's transparent half shows the band below.
|
|
@@ -50,6 +76,13 @@ const DIVISIONS = {
|
|
|
50
76
|
"background-position": "left top, right top, right bottom, left bottom",
|
|
51
77
|
"background-repeat": "no-repeat",
|
|
52
78
|
}),
|
|
79
|
+
|
|
80
|
+
// Across the whole height, not through the fess point, so the shield's point clips the last bar.
|
|
81
|
+
barry: stripes("bottom", 6),
|
|
82
|
+
"barry-of-eight": stripes("bottom", 8),
|
|
83
|
+
"barry-of-ten": stripes("bottom", 10),
|
|
84
|
+
paly: stripes("right", 6),
|
|
85
|
+
"paly-of-eight": stripes("right", 8),
|
|
53
86
|
};
|
|
54
87
|
|
|
55
88
|
// A division's background rules as one { image, size, position, repeat } per layer.
|
|
@@ -108,6 +141,7 @@ export default plugin(({ addUtilities, theme }) => {
|
|
|
108
141
|
utilities[`.${division}-${first}-${second}`] = {
|
|
109
142
|
...background(a, b),
|
|
110
143
|
...counterchange(background(b, a)),
|
|
144
|
+
"--field-first": a,
|
|
111
145
|
};
|
|
112
146
|
}
|
|
113
147
|
}
|
|
@@ -121,9 +155,11 @@ export default plugin(({ addUtilities, theme }) => {
|
|
|
121
155
|
[fur, tincture],
|
|
122
156
|
[tincture, fur],
|
|
123
157
|
]) {
|
|
158
|
+
// Only a tincture can be "of the first"; a fur first leaves it unset.
|
|
124
159
|
utilities[`.${division}-${first}-${second}`] = {
|
|
125
160
|
...withFur(division, first, second, theme),
|
|
126
161
|
...counterchange(withFur(division, second, first, theme)),
|
|
162
|
+
...(!FURS[first] && { "--field-first": `var(--color-${first})` }),
|
|
127
163
|
};
|
|
128
164
|
}
|
|
129
165
|
}
|
package/grailwind.css
CHANGED
|
@@ -42,6 +42,19 @@
|
|
|
42
42
|
syntax: "*";
|
|
43
43
|
inherits: false;
|
|
44
44
|
}
|
|
45
|
+
/* A field's first tincture. The escutcheon copies it to --the-first, which inherits,
|
|
46
|
+
* so a canton's own field can't change it. */
|
|
47
|
+
@property --field-first {
|
|
48
|
+
syntax: "*";
|
|
49
|
+
inherits: false;
|
|
50
|
+
}
|
|
51
|
+
/* A field's own bordure width, 0 without one. The shield copies it to --outer-inset, which
|
|
52
|
+
* inherits, so a canton can step out over the shield's bordure and not its own. */
|
|
53
|
+
@property --bordure-inset {
|
|
54
|
+
syntax: "<length>";
|
|
55
|
+
inherits: false;
|
|
56
|
+
initial-value: 0px;
|
|
57
|
+
}
|
|
45
58
|
|
|
46
59
|
/* ---------------------------------------------------------------------------
|
|
47
60
|
* Tinctures
|
|
@@ -67,24 +80,35 @@
|
|
|
67
80
|
* ------------------------------------------------------------------------- */
|
|
68
81
|
@utility or {
|
|
69
82
|
background-color: var(--color-or);
|
|
83
|
+
--field-first: var(--color-or);
|
|
70
84
|
}
|
|
71
85
|
@utility argent {
|
|
72
86
|
background-color: var(--color-argent);
|
|
87
|
+
--field-first: var(--color-argent);
|
|
73
88
|
}
|
|
74
89
|
@utility gules {
|
|
75
90
|
background-color: var(--color-gules);
|
|
91
|
+
--field-first: var(--color-gules);
|
|
76
92
|
}
|
|
77
93
|
@utility azure {
|
|
78
94
|
background-color: var(--color-azure);
|
|
95
|
+
--field-first: var(--color-azure);
|
|
79
96
|
}
|
|
80
97
|
@utility vert {
|
|
81
98
|
background-color: var(--color-vert);
|
|
99
|
+
--field-first: var(--color-vert);
|
|
82
100
|
}
|
|
83
101
|
@utility purpure {
|
|
84
102
|
background-color: var(--color-purpure);
|
|
103
|
+
--field-first: var(--color-purpure);
|
|
85
104
|
}
|
|
86
105
|
@utility sable {
|
|
87
106
|
background-color: var(--color-sable);
|
|
107
|
+
--field-first: var(--color-sable);
|
|
108
|
+
}
|
|
109
|
+
/* The field's first tincture, for charges and for a canton or inescutcheon field. */
|
|
110
|
+
@utility of-the-first {
|
|
111
|
+
background-color: var(--the-first, transparent);
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
/* ---------------------------------------------------------------------------
|
|
@@ -111,6 +135,12 @@
|
|
|
111
135
|
display: grid;
|
|
112
136
|
grid-template-columns: 1fr 1fr;
|
|
113
137
|
grid-template-rows: 1fr 1fr;
|
|
138
|
+
|
|
139
|
+
/* Each quarter is a coat of its own, so it starts its own count. */
|
|
140
|
+
& > * {
|
|
141
|
+
--the-first: var(--field-first);
|
|
142
|
+
--outer-inset: 0px;
|
|
143
|
+
}
|
|
114
144
|
}
|
|
115
145
|
|
|
116
146
|
/* ---------------------------------------------------------------------------
|
|
@@ -120,8 +150,51 @@
|
|
|
120
150
|
/* Bordure: a border in a given tincture, e.g. `bordure-or`. */
|
|
121
151
|
@utility bordure-* {
|
|
122
152
|
border-style: solid;
|
|
123
|
-
border-width: 0.75rem;
|
|
153
|
+
border-width: var(--bordure-width, 0.75rem);
|
|
124
154
|
border-color: --value(--color-*);
|
|
155
|
+
--bordure-inset: var(--bordure-width, 0.75rem);
|
|
156
|
+
}
|
|
157
|
+
@utility bordure-of-the-first {
|
|
158
|
+
border-style: solid;
|
|
159
|
+
border-width: var(--bordure-width, 0.75rem);
|
|
160
|
+
border-color: var(--the-first, transparent);
|
|
161
|
+
--bordure-inset: var(--bordure-width, 0.75rem);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Inescutcheon: a shield a third as wide, centred on the fess point (50%, 45%),
|
|
165
|
+
* over everything else on the shield. As both are 5:6, it's a third as tall too. */
|
|
166
|
+
@utility inescutcheon {
|
|
167
|
+
position: absolute;
|
|
168
|
+
z-index: 2;
|
|
169
|
+
width: 33%;
|
|
170
|
+
left: 33.5%;
|
|
171
|
+
top: 28.5%;
|
|
172
|
+
aspect-ratio: 5 / 6;
|
|
173
|
+
overflow: clip;
|
|
174
|
+
overflow-clip-margin: border-box;
|
|
175
|
+
border-radius: 0 0 50% 50% / 0 0 45% 45%;
|
|
176
|
+
box-shadow: 0 0 0 2px var(--color-sable);
|
|
177
|
+
contain: layout;
|
|
178
|
+
/* In proportion with the shield's own bordure. */
|
|
179
|
+
--bordure-width: 0.25rem;
|
|
180
|
+
--outer-inset: var(--bordure-inset);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* Canton: a third of the shield each way in dexter chief, so a ninth of it, and
|
|
184
|
+
* 5:6 like the shield, so everything on it places as on a shield. Over every
|
|
185
|
+
* ordinary and charge, as a later addition to the arms, and over the bordure. */
|
|
186
|
+
@utility canton {
|
|
187
|
+
position: absolute;
|
|
188
|
+
z-index: 2;
|
|
189
|
+
width: calc((100% + 2 * var(--outer-inset, 0px)) / 3);
|
|
190
|
+
left: calc(-1 * var(--outer-inset, 0px));
|
|
191
|
+
top: calc(-1 * var(--outer-inset, 0px));
|
|
192
|
+
aspect-ratio: 5 / 6;
|
|
193
|
+
/* Like the shield, so a chief on it surmounts its own bordure. */
|
|
194
|
+
overflow: clip;
|
|
195
|
+
overflow-clip-margin: border-box;
|
|
196
|
+
contain: layout;
|
|
197
|
+
--bordure-width: 0.25rem;
|
|
125
198
|
}
|
|
126
199
|
|
|
127
200
|
/* ---------------------------------------------------------------------------
|
|
@@ -132,11 +205,15 @@
|
|
|
132
205
|
@utility escutcheon {
|
|
133
206
|
position: relative;
|
|
134
207
|
aspect-ratio: 5 / 6;
|
|
135
|
-
|
|
208
|
+
/* At the border's edge, so a canton or chief can surmount the bordure. */
|
|
209
|
+
overflow: clip;
|
|
210
|
+
overflow-clip-margin: border-box;
|
|
136
211
|
border-radius: 0 0 50% 50% / 0 0 45% 45%;
|
|
137
212
|
box-shadow: 0 0 0 2px var(--color-sable);
|
|
138
213
|
/* The containing block for counterchanged charges' fixed layers. */
|
|
139
214
|
contain: layout;
|
|
215
|
+
--the-first: var(--field-first);
|
|
216
|
+
--outer-inset: var(--bordure-inset);
|
|
140
217
|
}
|
|
141
218
|
|
|
142
219
|
/* ---------------------------------------------------------------------------
|
package/ordinaries.js
CHANGED
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
* Grailwind CSS: ordinaries, e.g. `fess-or`.
|
|
3
3
|
*/
|
|
4
4
|
import plugin from "tailwindcss/plugin";
|
|
5
|
-
import { BETWEEN, FURS, ON, TINCTURES } from "./data.js";
|
|
5
|
+
import { BETWEEN, FURS, OF_THE_FIRST, ON, TINCTURES } from "./data.js";
|
|
6
6
|
import { furBackground } from "./furs.js";
|
|
7
7
|
|
|
8
8
|
// prettier-ignore
|
|
9
9
|
const ORDINARIES = {
|
|
10
10
|
pale: [[35, 0], [65, 0], [65, 100], [35, 100]],
|
|
11
11
|
fess: [[0, 30], [100, 30], [100, 60], [0, 60]],
|
|
12
|
+
// Bars that don't touch are one polygon, joined along the shield's dexter edge.
|
|
13
|
+
bar: [[0, 38], [100, 38], [100, 52], [0, 52]],
|
|
14
|
+
"two-bars": [[0, 27], [100, 27], [100, 39], [0, 39], [0, 51], [100, 51], [100, 63], [0, 63]],
|
|
15
|
+
"three-bars": [
|
|
16
|
+
[0, 20], [100, 20], [100, 30], [0, 30], [0, 40], [100, 40],
|
|
17
|
+
[100, 50], [0, 50], [0, 60], [100, 60], [100, 70], [0, 70],
|
|
18
|
+
],
|
|
12
19
|
bend: [[0, 0], [20, 0], [100, 80], [100, 100], [80, 100], [0, 20]],
|
|
13
20
|
"bend-sinister": [[100, 0], [80, 0], [0, 80], [0, 100], [20, 100], [100, 20]],
|
|
14
21
|
chevron: [[50, 25], [100, 75], [100, 95], [50, 45], [0, 95], [0, 75]],
|
|
@@ -25,6 +32,7 @@ const ORDINARIES = {
|
|
|
25
32
|
[60, 55], [60, 100], [40, 100], [40, 55], [0, 15],
|
|
26
33
|
],
|
|
27
34
|
chief: [[0, 0], [100, 0], [100, 30], [0, 30]],
|
|
35
|
+
pile: [[20, 0], [80, 0], [50, 85]],
|
|
28
36
|
};
|
|
29
37
|
|
|
30
38
|
const num = (v) => +v.toFixed(3);
|
|
@@ -34,6 +42,20 @@ const polygon = (vertices) => `polygon(${vertices.map(point).join(", ")})`;
|
|
|
34
42
|
|
|
35
43
|
const PSEUDO = { chief: "&::after" };
|
|
36
44
|
|
|
45
|
+
// The chief surmounts a bordure.
|
|
46
|
+
const INSET = "var(--bordure-inset)";
|
|
47
|
+
const OVER_BORDURE = {
|
|
48
|
+
"--bordure-inset": "inherit",
|
|
49
|
+
inset: `calc(-1 * ${INSET}) calc(-1 * ${INSET}) 0`,
|
|
50
|
+
padding: `${INSET} ${INSET} 0`,
|
|
51
|
+
"background-origin": "content-box",
|
|
52
|
+
};
|
|
53
|
+
// A vertex on the shield's top or side edges, moved out to the bordure's outer edge.
|
|
54
|
+
const outer = ([x, y]) => {
|
|
55
|
+
const ox = x === 0 ? `calc(-1 * ${INSET})` : x === 100 ? `calc(100% + ${INSET})` : pct(x);
|
|
56
|
+
return `${ox} ${y === 0 ? `calc(-1 * ${INSET})` : pct(y)}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
37
59
|
// A y in % of the field, as % of the whole shield when a chief takes the top of it.
|
|
38
60
|
const belowChief = (y) =>
|
|
39
61
|
y === 100 ? "100%" : `calc(${num(y)}% + var(--field-top, 0%) * ${num(1 - y / 100)})`;
|
|
@@ -104,10 +126,19 @@ const widenedPolygon = (vertices) => {
|
|
|
104
126
|
const S = 12;
|
|
105
127
|
const band = (kind, from, to, tile) => ({ kind, from, to, tile });
|
|
106
128
|
const stripe = (kind, from, to, tile) => ({ kind, from, to, tile, stripe: true });
|
|
129
|
+
// Half a tile, split on its diagonal, as gradient stops: 0–50% or 50–100%. The one diagonal band
|
|
130
|
+
// a tile that isn't square can take, as its diagonal isn't a line of constant x ± y.
|
|
131
|
+
const half = (kind, end, tile) => ({ kind, tile, stops: end === "start" ? [0, 50] : [50, 100] });
|
|
132
|
+
// A 2px strip down the centre, from the top to `bottom`, where two tiles meet mid-pixel and
|
|
133
|
+
// would let the fimbriation show between them. Unsoftened, as it lies on its own tincture.
|
|
134
|
+
const seam = (bottom) => ({ kind: "x", tile: [0, 0, 100, bottom], seam: true });
|
|
107
135
|
// prettier-ignore
|
|
108
136
|
const BANDS = {
|
|
109
137
|
pale: [band("x", 35, 65)],
|
|
110
138
|
fess: [band("y", 30, 60)],
|
|
139
|
+
bar: [band("y", 38, 52)],
|
|
140
|
+
"two-bars": [band("y", 27, 39), band("y", 51, 63)],
|
|
141
|
+
"three-bars": [band("y", 20, 30), band("y", 40, 50), band("y", 60, 70)],
|
|
111
142
|
bend: [band("u-v", -20, 20)],
|
|
112
143
|
"bend-sinister": [band("u+v", 80, 120)],
|
|
113
144
|
chevron: [
|
|
@@ -127,6 +158,8 @@ const BANDS = {
|
|
|
127
158
|
band("u+v", 85, 115),
|
|
128
159
|
band("x", 40, 60, [0, 55, 100, 100]),
|
|
129
160
|
],
|
|
161
|
+
// Each slanted side is its tile's diagonal.
|
|
162
|
+
pile: [half("u-v", "end", [20, 0, 50, 85]), half("u+v", "start", [50, 0, 80, 85]), seam(83)],
|
|
130
163
|
};
|
|
131
164
|
|
|
132
165
|
const DIRECTION = {
|
|
@@ -155,23 +188,24 @@ const stop = ({ kind, tile: [x0, y0, x1, y1] = [0, 0, 100, 100] }, value) => {
|
|
|
155
188
|
}
|
|
156
189
|
};
|
|
157
190
|
|
|
158
|
-
// Soften each edge by a pixel, as hard gradient stops aren't antialiased.
|
|
191
|
+
// Soften each edge by a pixel, as hard gradient stops aren't antialiased. A stop at 0 or 100% is
|
|
192
|
+
// the tile's own edge, so it isn't softened.
|
|
159
193
|
const gradient = (layer, colour) => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
194
|
+
if (layer.seam) {
|
|
195
|
+
return `linear-gradient(to right, transparent calc(50% - 1px), ${colour} 0 calc(50% + 1px), transparent 0)`;
|
|
196
|
+
}
|
|
197
|
+
const [a, b] = (layer.stops ?? [stop(layer, layer.from), stop(layer, layer.to)]).map(num);
|
|
198
|
+
const from =
|
|
199
|
+
a === 0 ? `${colour} 0` : `transparent calc(${a}% - 0.5px), ${colour} calc(${a}% + 0.5px)`;
|
|
200
|
+
const to = b === 100 ? "100%" : `calc(${b}% - 0.5px), transparent calc(${b}% + 0.5px)`;
|
|
201
|
+
return `linear-gradient(${DIRECTION[layer.kind]}, ${from} ${to})`;
|
|
166
202
|
};
|
|
167
203
|
|
|
168
204
|
const tilePosition = (start, size) => (size === 100 ? "0" : pct((start * 100) / (100 - size)));
|
|
169
205
|
|
|
170
|
-
const bands = (layers,
|
|
206
|
+
const bands = (layers, colour) => ({
|
|
171
207
|
"background-image": layers
|
|
172
|
-
.map((l) =>
|
|
173
|
-
gradient(l, l.stripe ? "var(--fimbriation, transparent)" : `var(--color-${tincture})`),
|
|
174
|
-
)
|
|
208
|
+
.map((l) => gradient(l, l.stripe ? "var(--fimbriation, transparent)" : colour))
|
|
175
209
|
.join(", "),
|
|
176
210
|
"background-size": layers
|
|
177
211
|
.map(({ tile: [x0, y0, x1, y1] = [0, 0, 100, 100] }) => `${pct(x1 - x0)} ${pct(y1 - y0)}`)
|
|
@@ -236,14 +270,14 @@ const crossing = (a, v, b, first, second) => {
|
|
|
236
270
|
: null;
|
|
237
271
|
};
|
|
238
272
|
|
|
239
|
-
|
|
273
|
+
// `extend` moves the sides on the shield's edge out, as `outer` does; the lined sides stay put.
|
|
274
|
+
const shape = (vertices, line, scallop, extend) => {
|
|
240
275
|
const inward = signedArea(vertices) > 0 ? "ccw" : "cw";
|
|
241
276
|
const pts = vertices.map(real);
|
|
277
|
+
const straight = vertices.map((v, i) => onShieldEdge(v, vertices[(i + 1) % vertices.length]));
|
|
242
278
|
const edges = pts.map((a, i) => {
|
|
243
279
|
const j = (i + 1) % pts.length;
|
|
244
|
-
return
|
|
245
|
-
? [{ to: pts[j] }]
|
|
246
|
-
: line(a, pts[j], scallop, inward);
|
|
280
|
+
return straight[i] ? [{ to: pts[j] }] : line(a, pts[j], scallop, inward);
|
|
247
281
|
});
|
|
248
282
|
|
|
249
283
|
// Where arcs cross at a corner, e.g. a chevron engrailed's peak, end both at the
|
|
@@ -266,22 +300,41 @@ const shape = (vertices, line, scallop) => {
|
|
|
266
300
|
}
|
|
267
301
|
});
|
|
268
302
|
|
|
269
|
-
const commands = edges
|
|
270
|
-
.
|
|
271
|
-
.map(({ to, r, sweep }) =>
|
|
303
|
+
const commands = edges.flatMap((edge, i) => {
|
|
304
|
+
const segments = edge.map(({ to, r, sweep }) =>
|
|
272
305
|
r
|
|
273
306
|
? `arc to ${at(to)} of ${pct((r / W) * 100)} ${pct((r / H) * 100)} ${sweep}`
|
|
274
307
|
: `line to ${at(to)}`,
|
|
275
308
|
);
|
|
276
|
-
|
|
309
|
+
if (!extend) {
|
|
310
|
+
return segments;
|
|
311
|
+
}
|
|
312
|
+
const outside = straight.at(i - 1);
|
|
313
|
+
if (straight[i]) {
|
|
314
|
+
const out = `line to ${extend(vertices[(i + 1) % vertices.length])}`;
|
|
315
|
+
return outside ? [out] : [`line to ${extend(vertices[i])}`, out];
|
|
316
|
+
}
|
|
317
|
+
return outside ? [`line to ${at(pts[i])}`, ...segments] : segments;
|
|
318
|
+
});
|
|
319
|
+
const from = extend ? extend(vertices[0]) : at(start);
|
|
320
|
+
return `shape(from ${from}, ${commands.join(", ")}, close)`;
|
|
277
321
|
};
|
|
278
322
|
|
|
279
|
-
const betweenVars = ({ size, points }) => {
|
|
323
|
+
const betweenVars = ({ size, points, counts = {} }) => {
|
|
280
324
|
const vars = { "--between-size": `${size}%` };
|
|
281
325
|
points.forEach(([left, top], i) => {
|
|
282
326
|
vars[`--between-${i + 1}-x`] = `${left}%`;
|
|
283
327
|
vars[`--between-${i + 1}-y`] = `${top}%`;
|
|
284
328
|
});
|
|
329
|
+
for (const [count, override] of Object.entries(counts)) {
|
|
330
|
+
if (override.size !== undefined) {
|
|
331
|
+
vars[`--between-${count}-size`] = `${override.size}%`;
|
|
332
|
+
}
|
|
333
|
+
override.points.forEach(([left, top], i) => {
|
|
334
|
+
vars[`--between-${count}-${i + 1}-x`] = `${left}%`;
|
|
335
|
+
vars[`--between-${count}-${i + 1}-y`] = `${top}%`;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
285
338
|
return vars;
|
|
286
339
|
};
|
|
287
340
|
|
|
@@ -296,13 +349,45 @@ const onVars = ({ size, counts }) => {
|
|
|
296
349
|
return vars;
|
|
297
350
|
};
|
|
298
351
|
|
|
299
|
-
|
|
352
|
+
// Every layout var an ordinary sets on its shield, for an inescutcheon or canton to reset.
|
|
353
|
+
const layoutVars = () => {
|
|
354
|
+
const allVars = [];
|
|
355
|
+
|
|
356
|
+
for (const division of Object.values(BETWEEN)) {
|
|
357
|
+
const vars = betweenVars(division);
|
|
358
|
+
allVars.push(...Object.keys(vars));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const division of Object.values(ON)) {
|
|
362
|
+
const vars = onVars(division);
|
|
363
|
+
allVars.push(...Object.keys(vars));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const chiefVars = ["--field-top", "--chief-points-y", "--chief-points-size"];
|
|
367
|
+
allVars.push(...chiefVars);
|
|
368
|
+
|
|
369
|
+
return [...new Set(allVars)];
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// Each tincture's class suffix and colour, and "of the first" (see --the-first in grailwind.css).
|
|
373
|
+
const PAINTS = [
|
|
374
|
+
...TINCTURES.map((tincture) => [tincture, `var(--color-${tincture})`]),
|
|
375
|
+
[OF_THE_FIRST, "var(--the-first, transparent)"],
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
export default plugin(({ addBase, addUtilities, theme }) => {
|
|
379
|
+
// Unset on an inescutcheon or canton, so it can't inherit its shield's layout. In the base
|
|
380
|
+
// layer, so an ordinary on it still sets its own.
|
|
381
|
+
addBase({
|
|
382
|
+
".inescutcheon, .canton": Object.fromEntries(layoutVars().map((name) => [name, "initial"])),
|
|
383
|
+
});
|
|
384
|
+
|
|
300
385
|
const utilities = {};
|
|
301
386
|
|
|
302
|
-
for (const tincture of
|
|
387
|
+
for (const [tincture, colour] of PAINTS) {
|
|
303
388
|
utilities[`.fimbriated-${tincture}`] = {
|
|
304
389
|
"--fimbriated": "1",
|
|
305
|
-
"--fimbriation":
|
|
390
|
+
"--fimbriation": colour,
|
|
306
391
|
};
|
|
307
392
|
}
|
|
308
393
|
|
|
@@ -329,10 +414,23 @@ export default plugin(({ addUtilities, theme }) => {
|
|
|
329
414
|
});
|
|
330
415
|
|
|
331
416
|
for (const [ordinary, vertices] of Object.entries(ORDINARIES)) {
|
|
417
|
+
const chief = ordinary === "chief";
|
|
418
|
+
const placed = chief ? OVER_BORDURE : { inset: "var(--field-top, 0%) 0 0" };
|
|
419
|
+
const plain = chief
|
|
420
|
+
? `polygon(${vertices.map(outer).join(", ")}) content-box`
|
|
421
|
+
: polygon(vertices);
|
|
422
|
+
// Browsers without shape() keep the straight polygon.
|
|
423
|
+
const lined = (line) => [
|
|
424
|
+
plain,
|
|
425
|
+
chief
|
|
426
|
+
? `${shape(vertices, line, SCALLOP, outer)} content-box`
|
|
427
|
+
: shape(vertices, line, SCALLOPS[ordinary] ?? SCALLOP),
|
|
428
|
+
];
|
|
429
|
+
|
|
332
430
|
// Covers the whole shield, so the swapped field lines up with the real one,
|
|
333
431
|
// and the clip moves the ordinary below any chief instead.
|
|
334
432
|
utilities[`.${ordinary}-counterchanged`] = ordinaryRules(ordinary, {
|
|
335
|
-
inset: "0",
|
|
433
|
+
...(chief ? OVER_BORDURE : { inset: "0" }),
|
|
336
434
|
// Registered not to inherit, so a shield inside another can't pick up its swap.
|
|
337
435
|
...Object.fromEntries(
|
|
338
436
|
["color", "image", "size", "position", "repeat"].map((n) => [
|
|
@@ -345,52 +443,50 @@ export default plugin(({ addUtilities, theme }) => {
|
|
|
345
443
|
"background-size": "var(--counterchange-size, auto)",
|
|
346
444
|
"background-position": "var(--counterchange-position, 0 0)",
|
|
347
445
|
"background-repeat": "var(--counterchange-repeat, repeat)",
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
446
|
+
// The swapped field's tiles would wrap into the bordure's strips, so the bordure shows there.
|
|
447
|
+
...(chief && { "background-clip": "content-box" }),
|
|
448
|
+
"clip-path": chief
|
|
449
|
+
? plain
|
|
450
|
+
: `polygon(${vertices.map(([x, y]) => `${pct(x)} ${belowChief(y)}`).join(", ")})`,
|
|
352
451
|
});
|
|
353
452
|
|
|
354
|
-
for (const tincture of
|
|
453
|
+
for (const [tincture, colour] of PAINTS) {
|
|
355
454
|
utilities[`.${ordinary}-${tincture}`] = ordinaryRules(ordinary, {
|
|
356
|
-
|
|
455
|
+
...placed,
|
|
357
456
|
...(BANDS[ordinary]
|
|
358
457
|
? {
|
|
359
458
|
"--fimbriated": "inherit",
|
|
360
459
|
"--fimbriation": "inherit",
|
|
361
|
-
"background-color": `var(--fimbriation,
|
|
362
|
-
...bands(BANDS[ordinary],
|
|
460
|
+
"background-color": `var(--fimbriation, ${colour})`,
|
|
461
|
+
...bands(BANDS[ordinary], colour),
|
|
363
462
|
"clip-path": widenedPolygon(vertices),
|
|
364
463
|
}
|
|
365
464
|
: {
|
|
366
|
-
"background-color":
|
|
367
|
-
"clip-path":
|
|
465
|
+
"background-color": colour,
|
|
466
|
+
"clip-path": plain,
|
|
368
467
|
}),
|
|
369
468
|
});
|
|
370
|
-
// Not fimbriated.
|
|
469
|
+
// Not fimbriated.
|
|
371
470
|
for (const [name, line] of Object.entries(LINES)) {
|
|
372
471
|
utilities[`.${ordinary}-${name}-${tincture}`] = ordinaryRules(ordinary, {
|
|
373
|
-
|
|
374
|
-
"background-color":
|
|
375
|
-
"clip-path":
|
|
472
|
+
...placed,
|
|
473
|
+
"background-color": colour,
|
|
474
|
+
"clip-path": lined(line),
|
|
376
475
|
});
|
|
377
476
|
}
|
|
378
477
|
}
|
|
379
478
|
|
|
380
479
|
// A plain polygon, without the fimbriation vars, so fimbriated-* can't reach a fur ordinary.
|
|
381
480
|
for (const fur of Object.keys(FURS)) {
|
|
382
|
-
const pseudo = {
|
|
383
|
-
inset: ordinary === "chief" ? "0" : "var(--field-top, 0%) 0 0",
|
|
384
|
-
...furBackground(fur, theme),
|
|
385
|
-
};
|
|
481
|
+
const pseudo = { ...placed, ...furBackground(fur, theme) };
|
|
386
482
|
utilities[`.${ordinary}-${fur}`] = ordinaryRules(ordinary, {
|
|
387
483
|
...pseudo,
|
|
388
|
-
"clip-path":
|
|
484
|
+
"clip-path": plain,
|
|
389
485
|
});
|
|
390
486
|
for (const [name, line] of Object.entries(LINES)) {
|
|
391
487
|
utilities[`.${ordinary}-${name}-${fur}`] = ordinaryRules(ordinary, {
|
|
392
488
|
...pseudo,
|
|
393
|
-
"clip-path":
|
|
489
|
+
"clip-path": lined(line),
|
|
394
490
|
});
|
|
395
491
|
}
|
|
396
492
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grailwind",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"build": "tailwindcss -i docs/app.css -o docs/site.css",
|
|
6
6
|
"dev": "tailwindcss -i docs/app.css -o docs/site.css --watch",
|
|
@@ -16,13 +16,17 @@
|
|
|
16
16
|
"tailwind",
|
|
17
17
|
"heraldry",
|
|
18
18
|
"blazon",
|
|
19
|
-
"css"
|
|
19
|
+
"css",
|
|
20
|
+
"coat-of-arms",
|
|
21
|
+
"coats-of-arms",
|
|
22
|
+
"tailwindcss-plugin",
|
|
23
|
+
"eslint"
|
|
20
24
|
],
|
|
21
25
|
"author": "Paul Firmin",
|
|
22
26
|
"license": "MIT",
|
|
23
27
|
"description": "Heraldic blazon for Tailwind CSS: tinctures, divisions, ordinaries and charges",
|
|
24
28
|
"devDependencies": {
|
|
25
|
-
"@grailwind/eslint-plugin": "^0.1.0-beta.
|
|
29
|
+
"@grailwind/eslint-plugin": "^0.1.0-beta.1",
|
|
26
30
|
"@html-eslint/parser": "^0.67.0",
|
|
27
31
|
"@tailwindcss/cli": "^4.3.3",
|
|
28
32
|
"eslint": "^10.14.0",
|