grailwind 0.6.0 → 0.7.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 +21 -1
- package/README.md +2 -2
- package/data.js +8 -0
- package/divisions.js +108 -16
- package/grailwind.css +21 -8
- package/package.json +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.7.0] - 2026-11-09
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `per-chevron-*`: a field divided along a chevron's centre line, the first
|
|
14
|
+
tincture in chief.
|
|
15
|
+
- `bendy-*`, `bendy-sinister-*`, `chevronny-*` (each with `-of-eight-*`) and
|
|
16
|
+
`chequy-*`: six or eight pieces, or five squares by six.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- `@grailwind/eslint-plugin` 0.1.0-beta.3 needs `grailwind` 0.7.x.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- On a shield, `quarterly` and two-part `per-fess` split at the fess point, as
|
|
25
|
+
the divisions do, not halfway down.
|
|
26
|
+
- No seam down the centre of `per-saltire-*` at odd widths.
|
|
27
|
+
|
|
9
28
|
## [0.6.0] - 2026-11-06
|
|
10
29
|
|
|
11
30
|
### Added
|
|
@@ -138,7 +157,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
138
157
|
Initial release: tinctures, divisions, ordinaries, charges, strict mode and
|
|
139
158
|
custom SVG charges.
|
|
140
159
|
|
|
141
|
-
[Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.
|
|
160
|
+
[Unreleased]: https://github.com/pafirmin/grailwind/compare/v0.7.0...HEAD
|
|
161
|
+
[0.7.0]: https://github.com/pafirmin/grailwind/compare/v0.6.0...v0.7.0
|
|
142
162
|
[0.6.0]: https://github.com/pafirmin/grailwind/compare/v0.5.0...v0.6.0
|
|
143
163
|
[0.5.0]: https://github.com/pafirmin/grailwind/compare/v0.4.0...v0.5.0
|
|
144
164
|
[0.4.0]: https://github.com/pafirmin/grailwind/compare/v0.3.0...v0.4.0
|
package/README.md
CHANGED
|
@@ -57,8 +57,8 @@ tinctures replace Tailwind's colours entirely.
|
|
|
57
57
|
- **The shield:** `escutcheon`, plus `bordure-*`, `inescutcheon` (a small shield at the centre)
|
|
58
58
|
and `canton` (a ninth of the shield in dexter chief), each with its own field and charges.
|
|
59
59
|
- **Divided fields:** `per-pale-or-gules`, `quarterly-azure-argent`,
|
|
60
|
-
`gyronny-or-sable`, `barry-argent-azure
|
|
61
|
-
layout.
|
|
60
|
+
`gyronny-or-sable`, `barry-argent-azure`, `bendy-argent-gules`, `chequy-or-azure` and more, or
|
|
61
|
+
`per-pale`, `per-fess` and `quarterly` for layout.
|
|
62
62
|
- **Ordinaries:** `fess-or`, `chevron-gules`, `cross-argent`, `chief-azure`,
|
|
63
63
|
`two-bars-or` and more, with `fimbriated-*` for a narrow border,
|
|
64
64
|
`fess-engrailed-or` and the like for an engrailed or invected edge,
|
package/data.js
CHANGED
|
@@ -13,6 +13,7 @@ export const DIVISIONS = [
|
|
|
13
13
|
"per-fess",
|
|
14
14
|
"per-bend",
|
|
15
15
|
"per-bend-sinister",
|
|
16
|
+
"per-chevron",
|
|
16
17
|
"per-saltire",
|
|
17
18
|
"quarterly",
|
|
18
19
|
"gyronny",
|
|
@@ -21,6 +22,13 @@ export const DIVISIONS = [
|
|
|
21
22
|
"barry-of-ten",
|
|
22
23
|
"paly",
|
|
23
24
|
"paly-of-eight",
|
|
25
|
+
"bendy",
|
|
26
|
+
"bendy-of-eight",
|
|
27
|
+
"bendy-sinister",
|
|
28
|
+
"bendy-sinister-of-eight",
|
|
29
|
+
"chevronny",
|
|
30
|
+
"chevronny-of-eight",
|
|
31
|
+
"chequy",
|
|
24
32
|
];
|
|
25
33
|
|
|
26
34
|
// Ordinaries that can be reversed, e.g. `pile-reversed-or`: the same shape upside down.
|
package/divisions.js
CHANGED
|
@@ -22,6 +22,57 @@ const stripes = (to, pieces) => (a, b) => {
|
|
|
22
22
|
};
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
// How much of the corner-to-corner extent the shield covers: the base corner is cut away by the
|
|
26
|
+
// border-radius of 0 0 50% 50% / 0 0 45% 45% and the 5:6 aspect ratio in grailwind.css. It runs
|
|
27
|
+
// from the chief corner the bands start in to the tangent on the base curve.
|
|
28
|
+
const BENDY_VISIBLE = (() => {
|
|
29
|
+
const [width, height] = [5, 6];
|
|
30
|
+
const [radiusX, radiusY] = [width / 2, 0.45 * height];
|
|
31
|
+
const [centreX, centreY] = [width / 2, height - radiusY];
|
|
32
|
+
const tangent = Math.sqrt((height * radiusX) ** 2 + (width * radiusY) ** 2);
|
|
33
|
+
return (width * height - height * centreX + width * centreY + tangent) / (2 * width * height);
|
|
34
|
+
})();
|
|
35
|
+
|
|
36
|
+
// Diagonal bands parallel to per bend, `pieces` equal ones across the visible part of the shield,
|
|
37
|
+
// the last running to the edge. Explicit stops, not a repeating gradient, so a canton's square
|
|
38
|
+
// corners don't show the first tincture again.
|
|
39
|
+
const bendy = (to, pieces) => (a, b) => {
|
|
40
|
+
const stops = [];
|
|
41
|
+
for (let i = 0; i < pieces; i++) {
|
|
42
|
+
const start = i === 0 ? 0 : ((i * BENDY_VISIBLE * 100) / pieces).toFixed(2);
|
|
43
|
+
const end = i === pieces - 1 ? 100 : (((i + 1) * BENDY_VISIBLE * 100) / pieces).toFixed(2);
|
|
44
|
+
stops.push(`${i % 2 === 0 ? a : b} ${start}% ${end}%`);
|
|
45
|
+
}
|
|
46
|
+
return layer(`linear-gradient(to ${to}, ${stops.join(", ")})`);
|
|
47
|
+
};
|
|
48
|
+
|
|
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.
|
|
56
|
+
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
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
25
76
|
const DIVISIONS = {
|
|
26
77
|
// Vertical split: first on the dexter (viewer's left).
|
|
27
78
|
"per-pale": (a, b) => layer(`linear-gradient(to right, ${a} 50%, ${b} 50%)`),
|
|
@@ -36,18 +87,39 @@ const DIVISIONS = {
|
|
|
36
87
|
// Line from sinister chief to dexter base: first in the upper half.
|
|
37
88
|
"per-bend-sinister": (a, b) => layer(`linear-gradient(to bottom right, ${a} 50%, ${b} 50%)`),
|
|
38
89
|
|
|
90
|
+
"per-chevron": (a, b) =>
|
|
91
|
+
a === "transparent"
|
|
92
|
+
? {
|
|
93
|
+
"background-image": [
|
|
94
|
+
`linear-gradient(to bottom right, ${a} 50%, ${b} 50%)`,
|
|
95
|
+
`linear-gradient(to bottom left, ${a} 50%, ${b} 50%)`,
|
|
96
|
+
`linear-gradient(${b}, ${b})`,
|
|
97
|
+
].join(", "),
|
|
98
|
+
"background-size": "50% 50%, 50% 50%, 100% 15%",
|
|
99
|
+
"background-position": "left 70%, right 70%, bottom",
|
|
100
|
+
"background-repeat": "no-repeat",
|
|
101
|
+
}
|
|
102
|
+
: {
|
|
103
|
+
"background-image": [
|
|
104
|
+
`linear-gradient(to bottom right, ${a} 42.5%, transparent 42.5%)`,
|
|
105
|
+
`linear-gradient(to bottom left, ${a} 42.5%, ${b} 42.5%)`,
|
|
106
|
+
].join(", "),
|
|
107
|
+
},
|
|
108
|
+
|
|
39
109
|
// 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.
|
|
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.
|
|
41
112
|
"per-saltire": (a, b) => ({
|
|
42
|
-
"background-color": b,
|
|
43
113
|
"background-image": [
|
|
44
114
|
`linear-gradient(to bottom left, ${a} 50%, ${b} 50%)`,
|
|
45
115
|
`linear-gradient(to bottom right, ${a} 50%, ${b} 50%)`,
|
|
46
116
|
`linear-gradient(to bottom left, ${b} 50%, ${a} 50%)`,
|
|
47
117
|
`linear-gradient(to bottom right, ${b} 50%, ${a} 50%)`,
|
|
118
|
+
`linear-gradient(${a}, ${a})`,
|
|
119
|
+
`linear-gradient(${b}, ${b})`,
|
|
48
120
|
].join(", "),
|
|
49
|
-
"background-size": "50% 50%",
|
|
50
|
-
"background-position": "left top, right top, right bottom, left bottom",
|
|
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",
|
|
51
123
|
"background-repeat": "no-repeat",
|
|
52
124
|
}),
|
|
53
125
|
|
|
@@ -83,22 +155,42 @@ const DIVISIONS = {
|
|
|
83
155
|
"barry-of-ten": stripes("bottom", 10),
|
|
84
156
|
paly: stripes("right", 6),
|
|
85
157
|
"paly-of-eight": stripes("right", 8),
|
|
158
|
+
|
|
159
|
+
// Bendy has the first tincture in the sinister chief corner, bendy sinister in the dexter.
|
|
160
|
+
bendy: bendy("bottom left", 6),
|
|
161
|
+
"bendy-of-eight": bendy("bottom left", 8),
|
|
162
|
+
"bendy-sinister": bendy("bottom right", 6),
|
|
163
|
+
"bendy-sinister-of-eight": bendy("bottom right", 8),
|
|
164
|
+
|
|
165
|
+
// The first tincture in the chief corners.
|
|
166
|
+
chevronny: chevronny(6),
|
|
167
|
+
"chevronny-of-eight": chevronny(8),
|
|
168
|
+
|
|
169
|
+
// 5 across and 6 down, the first tincture in the dexter chief square. Conic starts at the top.
|
|
170
|
+
// The repeat must be explicit, as with stripes.
|
|
171
|
+
chequy: (a, b) => ({
|
|
172
|
+
"background-image": `conic-gradient(${b} 0 25%, ${a} 0 50%, ${b} 0 75%, ${a} 0)`,
|
|
173
|
+
"background-size": "40% calc(100% / 3)",
|
|
174
|
+
"background-repeat": "repeat",
|
|
175
|
+
}),
|
|
86
176
|
};
|
|
87
177
|
|
|
88
178
|
// A division's background rules as one { image, size, position, repeat } per layer.
|
|
89
179
|
const toLayers = (rules) => {
|
|
90
|
-
return rules["background-image"]
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
180
|
+
return rules["background-image"]
|
|
181
|
+
.split(/, (?=(?:repeating-)?(?:linear|conic)-gradient)/)
|
|
182
|
+
.map((gradient, i) => {
|
|
183
|
+
const size = (rules["background-size"] ?? "100% 100%").split(", ");
|
|
184
|
+
const position = (rules["background-position"] ?? "0 0").split(", ");
|
|
185
|
+
const repeat = (rules["background-repeat"] ?? "no-repeat").split(", ");
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
image: gradient,
|
|
189
|
+
size: size[i % size.length],
|
|
190
|
+
position: position[i % position.length],
|
|
191
|
+
repeat: repeat[i % repeat.length],
|
|
192
|
+
};
|
|
193
|
+
});
|
|
102
194
|
};
|
|
103
195
|
|
|
104
196
|
// A division with one fur: the fur's part is left transparent, over the fur's own layers.
|
package/grailwind.css
CHANGED
|
@@ -135,10 +135,18 @@
|
|
|
135
135
|
grid-auto-flow: row;
|
|
136
136
|
grid-auto-rows: 1fr;
|
|
137
137
|
|
|
138
|
-
/*
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
/* On a shield, two parts split at the fess point, FESS_POINT in divisions.js.
|
|
139
|
+
* More parts stay equal. */
|
|
140
|
+
&:is(.escutcheon, .escutcheon *) {
|
|
141
|
+
&:has(> :last-child:nth-child(2)) {
|
|
142
|
+
grid-template-rows: 45fr 55fr;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* See per-pale. */
|
|
146
|
+
& > * {
|
|
147
|
+
position: relative;
|
|
148
|
+
contain: layout;
|
|
149
|
+
}
|
|
142
150
|
}
|
|
143
151
|
}
|
|
144
152
|
|
|
@@ -155,10 +163,15 @@
|
|
|
155
163
|
--outer-inset: 0px;
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
/*
|
|
159
|
-
&:is(.escutcheon, .escutcheon *)
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
/* On a shield, split at the fess point, as per-fess. */
|
|
167
|
+
&:is(.escutcheon, .escutcheon *) {
|
|
168
|
+
grid-template-rows: 45fr 55fr;
|
|
169
|
+
|
|
170
|
+
/* See per-pale. */
|
|
171
|
+
& > * {
|
|
172
|
+
position: relative;
|
|
173
|
+
contain: layout;
|
|
174
|
+
}
|
|
162
175
|
}
|
|
163
176
|
}
|
|
164
177
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grailwind",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"dev:watch": "tailwindcss -i docs/app.css -o docs/site.css --watch=always & python3 -m http.server ${PORT:-8000} -d docs",
|
|
10
10
|
"lint": "eslint .",
|
|
11
11
|
"format": "prettier --write .",
|
|
12
|
-
"format:check": "prettier --check ."
|
|
12
|
+
"format:check": "prettier --check .",
|
|
13
|
+
"prepare": "husky"
|
|
13
14
|
},
|
|
14
15
|
"keywords": [
|
|
15
16
|
"tailwindcss",
|
|
@@ -26,10 +27,12 @@
|
|
|
26
27
|
"license": "MIT",
|
|
27
28
|
"description": "Heraldic blazon for Tailwind CSS: tinctures, divisions, ordinaries and charges",
|
|
28
29
|
"devDependencies": {
|
|
29
|
-
"@grailwind/eslint-plugin": "^0.1.0-beta.
|
|
30
|
+
"@grailwind/eslint-plugin": "^0.1.0-beta.3",
|
|
30
31
|
"@html-eslint/parser": "^0.67.0",
|
|
31
32
|
"@tailwindcss/cli": "^4.3.3",
|
|
32
33
|
"eslint": "^10.14.0",
|
|
34
|
+
"husky": "^9.1.7",
|
|
35
|
+
"lint-staged": "^17.6.0",
|
|
33
36
|
"playwright": "~1.64.1",
|
|
34
37
|
"prettier": "^3.9.10",
|
|
35
38
|
"tailwindcss": "^4.3.3"
|
|
@@ -71,5 +74,8 @@
|
|
|
71
74
|
},
|
|
72
75
|
"bugs": {
|
|
73
76
|
"url": "https://github.com/pafirmin/grailwind/issues"
|
|
77
|
+
},
|
|
78
|
+
"lint-staged": {
|
|
79
|
+
"*": "prettier --write --ignore-unknown"
|
|
74
80
|
}
|
|
75
81
|
}
|