grailwind 0.8.0 → 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 +57 -1
- package/README.md +13 -8
- package/charges.js +50 -15
- package/data.js +95 -14
- package/divisions.js +139 -45
- package/furs.js +181 -23
- package/grailwind.css +214 -30
- package/ordinaries.js +466 -21
- package/package.json +2 -2
- package/paths.js +8 -0
package/furs.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Grailwind CSS: furs, as a tiled pattern
|
|
2
|
+
* Grailwind CSS: furs, as a tiled pattern over a ground tincture: ermine's spots, or the vair
|
|
3
|
+
* family's panes.
|
|
3
4
|
*/
|
|
4
5
|
import plugin from "tailwindcss/plugin";
|
|
5
|
-
import { FURS } from "./data.js";
|
|
6
|
+
import { FUR_PATTERNS, FURS } from "./data.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* An SVG image as a CSS background-image value.
|
|
10
|
+
* @param {string} viewBox The SVG's viewBox, e.g. "0 0 50 60".
|
|
11
|
+
* @param {string} fill A CSS colour, e.g. "#f2f2f2", for everything in `body`.
|
|
12
|
+
* @param {string} body SVG elements, drawn in `fill`.
|
|
13
|
+
* @returns {string} A CSS `url("data:image/svg+xml,…")`.
|
|
14
|
+
*/
|
|
15
|
+
const svg = (viewBox, fill, body) =>
|
|
16
|
+
`url("data:image/svg+xml,${encodeURIComponent(
|
|
17
|
+
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='${viewBox}'><g fill='${fill}'>${body}</g></svg>`,
|
|
18
|
+
)}")`;
|
|
6
19
|
|
|
7
20
|
const SPOT = [
|
|
8
21
|
`<circle cx='0' cy='-8.5' r='1.9'/><circle cx='-3.4' cy='-5.6' r='1.9'/><circle cx='3.4' cy='-5.6' r='1.9'/>`,
|
|
@@ -18,30 +31,170 @@ export const AT = [
|
|
|
18
31
|
[0, 60],
|
|
19
32
|
[50, 60],
|
|
20
33
|
];
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Ermine's tile: the spots, on a transparent ground.
|
|
36
|
+
* @param {string} fill A CSS colour for the spots.
|
|
37
|
+
* @returns {string} A CSS `url(…)`, from svg().
|
|
38
|
+
*/
|
|
39
|
+
const ermineTile = (fill) =>
|
|
40
|
+
svg(
|
|
41
|
+
"0 0 50 60",
|
|
42
|
+
fill,
|
|
43
|
+
AT.map(([x, y]) => `<g transform='translate(${x} ${y})'>${SPOT}</g>`).join(""),
|
|
44
|
+
);
|
|
27
45
|
|
|
28
|
-
// The
|
|
29
|
-
//
|
|
30
|
-
|
|
46
|
+
// The vair family's pane is PANE wide, and its row as high as its outline. Each outline is the
|
|
47
|
+
// pane's right side, from its top down to its base, about the pane's centre line; the left side
|
|
48
|
+
// mirrors it. Each side is symmetric about its own midpoint, so the gap between two panes is the
|
|
49
|
+
// same pane reversed.
|
|
50
|
+
const PANE = 30;
|
|
51
|
+
const VAIR_ROW = 32;
|
|
52
|
+
const SLOPE = VAIR_ROW / 4;
|
|
53
|
+
const POTENT_ROW = 18;
|
|
54
|
+
const OUTLINES = {
|
|
55
|
+
// Slope to the shoulder, upright side, flare to the base corner.
|
|
56
|
+
vair: [
|
|
57
|
+
[0, 0],
|
|
58
|
+
[PANE / 4, SLOPE],
|
|
59
|
+
[PANE / 4, VAIR_ROW - SLOPE],
|
|
60
|
+
[PANE / 2, VAIR_ROW],
|
|
61
|
+
],
|
|
62
|
+
// A T: the crossbar on top, twice the stem.
|
|
63
|
+
potent: [
|
|
64
|
+
[PANE / 3, 0],
|
|
65
|
+
[PANE / 3, POTENT_ROW / 2],
|
|
66
|
+
[PANE / 6, POTENT_ROW / 2],
|
|
67
|
+
[PANE / 6, POTENT_ROW],
|
|
68
|
+
],
|
|
69
|
+
};
|
|
31
70
|
|
|
32
|
-
|
|
33
|
-
|
|
71
|
+
/**
|
|
72
|
+
* @param {string} pattern A value of FUR_PATTERNS, e.g. "counter-vair".
|
|
73
|
+
* @returns {"ermine" | "vair" | "potent"} The family, which sets the pane's outline and density.
|
|
74
|
+
*/
|
|
75
|
+
const family = (pattern) =>
|
|
76
|
+
pattern.endsWith("potent") ? "potent" : pattern.endsWith("vair") ? "vair" : "ermine";
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* One pane, pointing up, with its top centre at (0, 0).
|
|
80
|
+
* @param {number[][]} outline The pane's right side as [x, y] points, e.g. OUTLINES.vair.
|
|
81
|
+
* @returns {string} An SVG `<path>`, with no fill of its own.
|
|
82
|
+
*/
|
|
83
|
+
const pane = (outline) => {
|
|
84
|
+
const right = outline.map(([x, y]) => `${x},${y}`);
|
|
85
|
+
const left = outline.toReversed().map(([x, y]) => `${-x},${y}`);
|
|
86
|
+
return `<path d='M${[...right, ...left].join(" L")} Z'/>`;
|
|
87
|
+
};
|
|
34
88
|
|
|
89
|
+
/**
|
|
90
|
+
* The vair family's tile, PANE wide and two rows high. Only the first tincture's panes are drawn;
|
|
91
|
+
* the ground shows through the gaps between them as the second tincture's panes.
|
|
92
|
+
*
|
|
93
|
+
* Row 1 has one pane, centred. Row 2 is offset half a pane in vair and potent, drawn at both edges
|
|
94
|
+
* so the halves join across tiles; in the counter- forms it's mirrored, base to base with row 1.
|
|
95
|
+
*
|
|
96
|
+
* @param {"vair" | "counter-vair" | "potent" | "counter-potent"} pattern
|
|
97
|
+
* @param {string} fill A CSS colour for the drawn panes, e.g. argent's "#f2f2f2" in vair.
|
|
98
|
+
* @returns {string} A CSS `url(…)`, from svg().
|
|
99
|
+
*/
|
|
100
|
+
const paneTile = (pattern, fill) => {
|
|
101
|
+
const outline = OUTLINES[family(pattern)];
|
|
102
|
+
const row = outline.at(-1)[1];
|
|
103
|
+
const at = (x, y, path) => `<g transform='translate(${x} ${y})'>${path}</g>`;
|
|
104
|
+
const viewBox = `0 0 ${PANE} ${2 * row}`;
|
|
105
|
+
if (pattern.startsWith("counter-")) {
|
|
106
|
+
// Both rows as one outline: two paths meeting base to base leave a hairline where each one's
|
|
107
|
+
// edge is antialiased.
|
|
108
|
+
const mirrored = outline.toReversed().map(([x, y]) => [x, 2 * row - y]);
|
|
109
|
+
return svg(viewBox, fill, at(PANE / 2, 0, pane([...outline, ...mirrored])));
|
|
110
|
+
}
|
|
111
|
+
const path = pane(outline);
|
|
112
|
+
return svg(
|
|
113
|
+
viewBox,
|
|
114
|
+
fill,
|
|
115
|
+
[at(PANE / 2, 0, path), at(0, row, path), at(PANE, row, path)].join(""),
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A fur's tile. The SVG can't read CSS variables, so it takes the theme's colour at build time.
|
|
121
|
+
* @param {string} fur A key of FURS, e.g. "vairy-or-gules".
|
|
122
|
+
* @param {Function} theme Tailwind's theme() lookup.
|
|
123
|
+
* @returns {string} A CSS `url(…)` of the drawn tincture only; the ground isn't in it.
|
|
124
|
+
*/
|
|
125
|
+
const tile = (fur, theme) => {
|
|
126
|
+
const pattern = FUR_PATTERNS[fur];
|
|
127
|
+
const fill = theme(`colors.${FURS[fur][1]}`);
|
|
128
|
+
return pattern === "ermine" ? ermineTile(fill) : paneTile(pattern, fill);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* `fur-<n>` sets --fur-across, n whole tiles across the field. It's left unregistered, so each
|
|
133
|
+
* family keeps its own default: 6 for ermine; 4 for vair, which Fox-Davies calls vair, between
|
|
134
|
+
* beffroi (3) and menu-vair (6); and 3 for potent, as in Fox-Davies' and Parker's figures.
|
|
135
|
+
* @param {number} fallback Tiles across when no `fur-<n>` is set.
|
|
136
|
+
* @returns {string} A CSS var() for the number of tiles across.
|
|
137
|
+
*/
|
|
138
|
+
const across = (fallback) => `var(--fur-across, ${fallback})`;
|
|
35
139
|
// A quarter of a tile up and left, so whole spots sit along the top and sides. A % position is a
|
|
36
|
-
// share of (box - tile), which is (
|
|
37
|
-
const SHIFT =
|
|
38
|
-
const
|
|
140
|
+
// share of (box - tile), which is (n - 1) tiles.
|
|
141
|
+
const SHIFT = `calc(-100% / (4 * (${across(6)} - 1)))`;
|
|
142
|
+
const FIELDS = {
|
|
143
|
+
ermine: { size: `calc(100% / ${across(6)}) auto`, position: `${SHIFT} ${SHIFT}` },
|
|
144
|
+
// A whole number of panes, from the top left.
|
|
145
|
+
vair: { size: `calc(100% / ${across(4)}) auto`, position: "0 0" },
|
|
146
|
+
potent: { size: `calc(100% / ${across(3)}) auto`, position: "0 0" },
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* @param {string} fur A key of FURS.
|
|
150
|
+
* @returns {{ size: string, position: string }} The CSS background-size and -position of its tile.
|
|
151
|
+
*/
|
|
152
|
+
const field = (fur) => FIELDS[family(FUR_PATTERNS[fur])];
|
|
39
153
|
|
|
40
|
-
//
|
|
154
|
+
// Ermine's spots on a bordure are 1.4 bordure widths across. At the default eighth, the side
|
|
155
|
+
// columns are then on the ring's centre lines (3.5 widths = 5 half-tiles), whatever the width.
|
|
156
|
+
const RING_ERMINE_TILE = 1.4;
|
|
157
|
+
// A bordure's panes are one row across its width, so the tile is as wide as the pane's share of it.
|
|
158
|
+
const RINGS = {
|
|
159
|
+
ermine: {
|
|
160
|
+
size: `calc(${RING_ERMINE_TILE} * var(--bordure-width)) auto`,
|
|
161
|
+
position: `center calc(var(--bordure-width) / 2)`,
|
|
162
|
+
},
|
|
163
|
+
vair: { size: `calc(var(--bordure-width) * ${PANE} / ${VAIR_ROW}) auto`, position: "center 0" },
|
|
164
|
+
potent: {
|
|
165
|
+
size: `calc(var(--bordure-width) * ${PANE} / ${POTENT_ROW}) auto`,
|
|
166
|
+
position: "center 0",
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A fur on a frame's bordure ring, sized to the bordure's width, not to `fur-<n>`.
|
|
172
|
+
* @param {string} fur A key of FURS.
|
|
173
|
+
* @param {Function} theme Tailwind's theme() lookup.
|
|
174
|
+
* @returns {Record<string, string>} CSS background declarations for the ring's ::after.
|
|
175
|
+
*/
|
|
176
|
+
export const ringBackground = (fur, theme) => {
|
|
177
|
+
const ring = RINGS[family(FUR_PATTERNS[fur])];
|
|
178
|
+
return {
|
|
179
|
+
"background-color": `var(--color-${FURS[fur][0]})`,
|
|
180
|
+
"background-image": tile(fur, theme),
|
|
181
|
+
"background-size": ring.size,
|
|
182
|
+
"background-position": ring.position,
|
|
183
|
+
"background-repeat": "repeat",
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The layers under a fur in a divided field: the pattern, then the ground.
|
|
189
|
+
* @param {string} fur A key of FURS.
|
|
190
|
+
* @param {Function} theme Tailwind's theme() lookup.
|
|
191
|
+
* @returns {{ image: string, size: string, position: string, repeat: string }[]} Background
|
|
192
|
+
* layers, top first.
|
|
193
|
+
*/
|
|
41
194
|
export const furLayers = (fur, theme) => {
|
|
42
195
|
const ground = `var(--color-${FURS[fur][0]})`;
|
|
43
196
|
return [
|
|
44
|
-
{ image:
|
|
197
|
+
{ image: tile(fur, theme), ...field(fur), repeat: "repeat" },
|
|
45
198
|
{
|
|
46
199
|
image: `linear-gradient(${ground}, ${ground})`,
|
|
47
200
|
size: "100% 100%",
|
|
@@ -51,12 +204,17 @@ export const furLayers = (fur, theme) => {
|
|
|
51
204
|
];
|
|
52
205
|
};
|
|
53
206
|
|
|
54
|
-
|
|
207
|
+
/**
|
|
208
|
+
* A fur on its own, as on the field or an ordinary.
|
|
209
|
+
* @param {string} fur A key of FURS.
|
|
210
|
+
* @param {Function} theme Tailwind's theme() lookup.
|
|
211
|
+
* @returns {Record<string, string>} CSS background declarations.
|
|
212
|
+
*/
|
|
55
213
|
export const furBackground = (fur, theme) => ({
|
|
56
214
|
"background-color": `var(--color-${FURS[fur][0]})`,
|
|
57
|
-
"background-image":
|
|
58
|
-
"background-size":
|
|
59
|
-
"background-position":
|
|
215
|
+
"background-image": tile(fur, theme),
|
|
216
|
+
"background-size": field(fur).size,
|
|
217
|
+
"background-position": field(fur).position,
|
|
60
218
|
"background-repeat": "repeat",
|
|
61
219
|
});
|
|
62
220
|
|
|
@@ -66,7 +224,7 @@ export default plugin(({ addUtilities, theme }) => {
|
|
|
66
224
|
for (const fur of Object.keys(FURS)) {
|
|
67
225
|
utilities[`.${fur}`] = {
|
|
68
226
|
...furBackground(fur, theme),
|
|
69
|
-
// Two tiles across a charge, centred, so a whole spot
|
|
227
|
+
// Two tiles across a charge, centred, so ermine has a whole spot in its middle.
|
|
70
228
|
".charges > &": {
|
|
71
229
|
"background-size": "50% auto",
|
|
72
230
|
"background-position": "center",
|
package/grailwind.css
CHANGED
|
@@ -42,19 +42,73 @@
|
|
|
42
42
|
syntax: "*";
|
|
43
43
|
inherits: false;
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
@property --field-image {
|
|
46
|
+
syntax: "*";
|
|
47
|
+
inherits: false;
|
|
48
|
+
}
|
|
49
|
+
@property --field-size {
|
|
50
|
+
syntax: "*";
|
|
51
|
+
inherits: false;
|
|
52
|
+
}
|
|
53
|
+
@property --field-position {
|
|
54
|
+
syntax: "*";
|
|
55
|
+
inherits: false;
|
|
56
|
+
}
|
|
57
|
+
@property --field-repeat {
|
|
58
|
+
syntax: "*";
|
|
59
|
+
inherits: false;
|
|
60
|
+
}
|
|
61
|
+
@property --on-counterchange-color {
|
|
62
|
+
syntax: "*";
|
|
63
|
+
inherits: false;
|
|
64
|
+
}
|
|
65
|
+
@property --on-counterchange-image {
|
|
66
|
+
syntax: "*";
|
|
67
|
+
inherits: false;
|
|
68
|
+
}
|
|
69
|
+
@property --on-counterchange-size {
|
|
70
|
+
syntax: "*";
|
|
71
|
+
inherits: false;
|
|
72
|
+
}
|
|
73
|
+
@property --on-counterchange-position {
|
|
74
|
+
syntax: "*";
|
|
75
|
+
inherits: false;
|
|
76
|
+
}
|
|
77
|
+
@property --on-counterchange-repeat {
|
|
78
|
+
syntax: "*";
|
|
79
|
+
inherits: false;
|
|
80
|
+
}
|
|
81
|
+
/* A field's first tincture. The heater's field copies it to --the-first, which inherits,
|
|
46
82
|
* so a canton's own field can't change it. */
|
|
47
83
|
@property --field-first {
|
|
48
84
|
syntax: "*";
|
|
49
85
|
inherits: false;
|
|
50
86
|
}
|
|
51
|
-
/*
|
|
87
|
+
/* The shield's first tincture, on the heater, for a bordure ring of the first. It inherits, so
|
|
88
|
+
* the ring on a frame's ::after sees it, an inescutcheon's included: of the first is the
|
|
89
|
+
* shield's. */
|
|
90
|
+
@property --frame-first {
|
|
91
|
+
syntax: "*";
|
|
92
|
+
inherits: true;
|
|
93
|
+
}
|
|
94
|
+
/* A field's own bordure width, 0 without one. A frame's field copies it to --outer-inset, which
|
|
52
95
|
* inherits, so a canton can step out over the shield's bordure and not its own. */
|
|
53
96
|
@property --bordure-inset {
|
|
54
97
|
syntax: "<length>";
|
|
55
98
|
inherits: false;
|
|
56
99
|
initial-value: 0px;
|
|
57
100
|
}
|
|
101
|
+
/* A part's bordure has none on its palar side: 0 there, and the chief steps out by it. Unset
|
|
102
|
+
* elsewhere, so the chief falls back to --bordure-inset, and not inherited, so a canton's own
|
|
103
|
+
* doesn't take its part's. */
|
|
104
|
+
@property --bordure-inset-dexter {
|
|
105
|
+
syntax: "*";
|
|
106
|
+
inherits: false;
|
|
107
|
+
}
|
|
108
|
+
@property --bordure-inset-sinister {
|
|
109
|
+
syntax: "*";
|
|
110
|
+
inherits: false;
|
|
111
|
+
}
|
|
58
112
|
|
|
59
113
|
/* ---------------------------------------------------------------------------
|
|
60
114
|
* Tinctures
|
|
@@ -81,30 +135,51 @@
|
|
|
81
135
|
@utility or {
|
|
82
136
|
background-color: var(--color-or);
|
|
83
137
|
--field-first: var(--color-or);
|
|
138
|
+
.heater:has(> &) {
|
|
139
|
+
--frame-first: var(--color-or);
|
|
140
|
+
}
|
|
84
141
|
}
|
|
85
142
|
@utility argent {
|
|
86
143
|
background-color: var(--color-argent);
|
|
87
144
|
--field-first: var(--color-argent);
|
|
145
|
+
.heater:has(> &) {
|
|
146
|
+
--frame-first: var(--color-argent);
|
|
147
|
+
}
|
|
88
148
|
}
|
|
89
149
|
@utility gules {
|
|
90
150
|
background-color: var(--color-gules);
|
|
91
151
|
--field-first: var(--color-gules);
|
|
152
|
+
.heater:has(> &) {
|
|
153
|
+
--frame-first: var(--color-gules);
|
|
154
|
+
}
|
|
92
155
|
}
|
|
93
156
|
@utility azure {
|
|
94
157
|
background-color: var(--color-azure);
|
|
95
158
|
--field-first: var(--color-azure);
|
|
159
|
+
.heater:has(> &) {
|
|
160
|
+
--frame-first: var(--color-azure);
|
|
161
|
+
}
|
|
96
162
|
}
|
|
97
163
|
@utility vert {
|
|
98
164
|
background-color: var(--color-vert);
|
|
99
165
|
--field-first: var(--color-vert);
|
|
166
|
+
.heater:has(> &) {
|
|
167
|
+
--frame-first: var(--color-vert);
|
|
168
|
+
}
|
|
100
169
|
}
|
|
101
170
|
@utility purpure {
|
|
102
171
|
background-color: var(--color-purpure);
|
|
103
172
|
--field-first: var(--color-purpure);
|
|
173
|
+
.heater:has(> &) {
|
|
174
|
+
--frame-first: var(--color-purpure);
|
|
175
|
+
}
|
|
104
176
|
}
|
|
105
177
|
@utility sable {
|
|
106
178
|
background-color: var(--color-sable);
|
|
107
179
|
--field-first: var(--color-sable);
|
|
180
|
+
.heater:has(> &) {
|
|
181
|
+
--frame-first: var(--color-sable);
|
|
182
|
+
}
|
|
108
183
|
}
|
|
109
184
|
/* The field's first tincture, for charges and for a canton or inescutcheon field. */
|
|
110
185
|
@utility of-the-first {
|
|
@@ -115,7 +190,9 @@
|
|
|
115
190
|
* Field divisions
|
|
116
191
|
* ------------------------------------------------------------------------- */
|
|
117
192
|
|
|
118
|
-
/* Per pale: split vertically into dexter | sinister.
|
|
193
|
+
/* Per pale: split vertically into dexter | sinister. A split field's parts are its children that
|
|
194
|
+
* aren't charges, an inescutcheon or a canton: those lie over all of it. The same selector is
|
|
195
|
+
* PART in ordinaries.js. */
|
|
119
196
|
@utility per-pale {
|
|
120
197
|
display: grid;
|
|
121
198
|
grid-auto-flow: column;
|
|
@@ -123,9 +200,10 @@
|
|
|
123
200
|
|
|
124
201
|
/* On a shield, each part is a coat of its own: what's in it is placed, stacked and
|
|
125
202
|
* counterchanged within it. */
|
|
126
|
-
&:is(.
|
|
203
|
+
&:is(.heater, .heater *) > :not(.charges, .inescutcheon, .canton) {
|
|
127
204
|
position: relative;
|
|
128
205
|
contain: layout;
|
|
206
|
+
--layer-inset: 0px;
|
|
129
207
|
}
|
|
130
208
|
}
|
|
131
209
|
|
|
@@ -137,15 +215,20 @@
|
|
|
137
215
|
|
|
138
216
|
/* On a shield, two parts split at the fess point, FESS_POINT in divisions.js.
|
|
139
217
|
* More parts stay equal. */
|
|
140
|
-
&:is(.
|
|
141
|
-
&:has(
|
|
218
|
+
&:is(.heater, .heater *) {
|
|
219
|
+
&:has(
|
|
220
|
+
> :nth-child(2 of :not(.charges, .inescutcheon, .canton)):nth-last-child(
|
|
221
|
+
1 of :not(.charges, .inescutcheon, .canton)
|
|
222
|
+
)
|
|
223
|
+
) {
|
|
142
224
|
grid-template-rows: 45fr 55fr;
|
|
143
225
|
}
|
|
144
226
|
|
|
145
227
|
/* See per-pale. */
|
|
146
|
-
& >
|
|
228
|
+
& > :not(.charges, .inescutcheon, .canton) {
|
|
147
229
|
position: relative;
|
|
148
230
|
contain: layout;
|
|
231
|
+
--layer-inset: 0px;
|
|
149
232
|
}
|
|
150
233
|
}
|
|
151
234
|
}
|
|
@@ -158,19 +241,20 @@
|
|
|
158
241
|
grid-template-rows: 1fr 1fr;
|
|
159
242
|
|
|
160
243
|
/* Each quarter is a coat of its own, so it starts its own count. */
|
|
161
|
-
& >
|
|
244
|
+
& > :not(.charges, .inescutcheon, .canton) {
|
|
162
245
|
--the-first: var(--field-first);
|
|
163
246
|
--outer-inset: 0px;
|
|
164
247
|
}
|
|
165
248
|
|
|
166
249
|
/* On a shield, split at the fess point, as per-fess. */
|
|
167
|
-
&:is(.
|
|
250
|
+
&:is(.heater, .heater *) {
|
|
168
251
|
grid-template-rows: 45fr 55fr;
|
|
169
252
|
|
|
170
253
|
/* See per-pale. */
|
|
171
|
-
& >
|
|
254
|
+
& > :not(.charges, .inescutcheon, .canton) {
|
|
172
255
|
position: relative;
|
|
173
256
|
contain: layout;
|
|
257
|
+
--layer-inset: 0px;
|
|
174
258
|
}
|
|
175
259
|
}
|
|
176
260
|
}
|
|
@@ -179,37 +263,117 @@
|
|
|
179
263
|
* Subordinaries
|
|
180
264
|
* ------------------------------------------------------------------------- */
|
|
181
265
|
|
|
182
|
-
/* Bordure
|
|
266
|
+
/* Bordure, e.g. `bordure-or`: a border, so what's inside it lays out within it. On a frame's
|
|
267
|
+
* field the border is transparent, and a ring on the frame's ::after (see ordinaries.js)
|
|
268
|
+
* paints it, following the frame's outline. On a per-pale part the ring is on that half, and
|
|
269
|
+
* the part has no border on the palar side. A chief on the part goes under the ring. Anywhere
|
|
270
|
+
* else, such as on a quarter or a canton, the border is painted. */
|
|
183
271
|
@utility bordure-* {
|
|
184
272
|
border-style: solid;
|
|
185
273
|
border-width: var(--bordure-width, 0.75rem);
|
|
186
274
|
border-color: --value(--color-*);
|
|
187
275
|
--bordure-inset: var(--bordure-width, 0.75rem);
|
|
276
|
+
|
|
277
|
+
:is(.heater, .inescutcheon) > & {
|
|
278
|
+
border-color: transparent;
|
|
279
|
+
}
|
|
280
|
+
:is(.heater, .inescutcheon):has(> &)::after {
|
|
281
|
+
content: "";
|
|
282
|
+
background-color: --value(--color-*);
|
|
283
|
+
}
|
|
284
|
+
:is(.heater, .inescutcheon)
|
|
285
|
+
> .per-pale
|
|
286
|
+
> &:nth-child(1 of :not(.charges, .inescutcheon, .canton)) {
|
|
287
|
+
border-color: transparent;
|
|
288
|
+
border-right-width: 0;
|
|
289
|
+
--bordure-inset-sinister: 0px;
|
|
290
|
+
}
|
|
291
|
+
:is(.heater, .inescutcheon):has(
|
|
292
|
+
> .per-pale > &:nth-child(1 of :not(.charges, .inescutcheon, .canton))
|
|
293
|
+
)::after {
|
|
294
|
+
content: "";
|
|
295
|
+
--ring-dexter: --value(--color-*);
|
|
296
|
+
}
|
|
297
|
+
:is(.heater, .inescutcheon)
|
|
298
|
+
> .per-pale
|
|
299
|
+
> &:nth-last-child(1 of :not(.charges, .inescutcheon, .canton)) {
|
|
300
|
+
border-color: transparent;
|
|
301
|
+
border-left-width: 0;
|
|
302
|
+
--bordure-inset-dexter: 0px;
|
|
303
|
+
}
|
|
304
|
+
:is(.heater, .inescutcheon):has(
|
|
305
|
+
> .per-pale
|
|
306
|
+
> &:nth-last-child(1 of :not(.charges, .inescutcheon, .canton))
|
|
307
|
+
)::after {
|
|
308
|
+
content: "";
|
|
309
|
+
--ring-sinister: --value(--color-*);
|
|
310
|
+
}
|
|
188
311
|
}
|
|
189
312
|
@utility bordure-of-the-first {
|
|
190
313
|
border-style: solid;
|
|
191
314
|
border-width: var(--bordure-width, 0.75rem);
|
|
192
315
|
border-color: var(--the-first, transparent);
|
|
193
316
|
--bordure-inset: var(--bordure-width, 0.75rem);
|
|
317
|
+
|
|
318
|
+
:is(.heater, .inescutcheon) > & {
|
|
319
|
+
border-color: transparent;
|
|
320
|
+
}
|
|
321
|
+
:is(.heater, .inescutcheon):has(> &)::after {
|
|
322
|
+
content: "";
|
|
323
|
+
background-color: var(--frame-first, transparent);
|
|
324
|
+
}
|
|
325
|
+
:is(.heater, .inescutcheon)
|
|
326
|
+
> .per-pale
|
|
327
|
+
> &:nth-child(1 of :not(.charges, .inescutcheon, .canton)) {
|
|
328
|
+
border-color: transparent;
|
|
329
|
+
border-right-width: 0;
|
|
330
|
+
--bordure-inset-sinister: 0px;
|
|
331
|
+
}
|
|
332
|
+
:is(.heater, .inescutcheon):has(
|
|
333
|
+
> .per-pale > &:nth-child(1 of :not(.charges, .inescutcheon, .canton))
|
|
334
|
+
)::after {
|
|
335
|
+
content: "";
|
|
336
|
+
--ring-dexter: var(--frame-first, transparent);
|
|
337
|
+
}
|
|
338
|
+
:is(.heater, .inescutcheon)
|
|
339
|
+
> .per-pale
|
|
340
|
+
> &:nth-last-child(1 of :not(.charges, .inescutcheon, .canton)) {
|
|
341
|
+
border-color: transparent;
|
|
342
|
+
border-left-width: 0;
|
|
343
|
+
--bordure-inset-dexter: 0px;
|
|
344
|
+
}
|
|
345
|
+
:is(.heater, .inescutcheon):has(
|
|
346
|
+
> .per-pale
|
|
347
|
+
> &:nth-last-child(1 of :not(.charges, .inescutcheon, .canton))
|
|
348
|
+
)::after {
|
|
349
|
+
content: "";
|
|
350
|
+
--ring-sinister: var(--frame-first, transparent);
|
|
351
|
+
}
|
|
194
352
|
}
|
|
195
353
|
|
|
196
|
-
/* Inescutcheon: a
|
|
197
|
-
* over everything else on the shield. As both are 5:6, it's a third as tall too.
|
|
354
|
+
/* Inescutcheon: a heater a third as wide round its own field, centred on the fess point
|
|
355
|
+
* (50%, 45%), over everything else on the shield. As both are 5:6, it's a third as tall too.
|
|
356
|
+
* Its field takes the shield's first, as "of the first" means the shield's. */
|
|
198
357
|
@utility inescutcheon {
|
|
199
358
|
position: absolute;
|
|
200
|
-
z-index:
|
|
359
|
+
z-index: 5;
|
|
201
360
|
width: 33%;
|
|
202
361
|
left: 33.5%;
|
|
203
362
|
top: 28.5%;
|
|
363
|
+
display: grid;
|
|
204
364
|
aspect-ratio: 5 / 6;
|
|
205
365
|
overflow: clip;
|
|
206
|
-
overflow-clip-margin: border-box;
|
|
207
|
-
border-radius: 0 0 50% 50% / 0 0 45% 45%;
|
|
208
|
-
box-shadow: 0 0 0 2px var(--color-sable);
|
|
209
366
|
contain: layout;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
--
|
|
367
|
+
container-type: inline-size;
|
|
368
|
+
/* An eighth of its own width, like the shield's. */
|
|
369
|
+
--bordure-width: calc(100cqw / 8);
|
|
370
|
+
|
|
371
|
+
/* See heater. */
|
|
372
|
+
& > * {
|
|
373
|
+
position: relative;
|
|
374
|
+
--outer-inset: var(--bordure-inset);
|
|
375
|
+
--layer-inset: var(--bordure-inset);
|
|
376
|
+
}
|
|
213
377
|
}
|
|
214
378
|
|
|
215
379
|
/* Canton: a third of the shield each way in dexter chief, so a ninth of it, and
|
|
@@ -217,7 +381,7 @@
|
|
|
217
381
|
* ordinary and charge, as a later addition to the arms, and over the bordure. */
|
|
218
382
|
@utility canton {
|
|
219
383
|
position: absolute;
|
|
220
|
-
z-index:
|
|
384
|
+
z-index: 5;
|
|
221
385
|
width: calc((100% + 2 * var(--outer-inset, 0px)) / 3);
|
|
222
386
|
left: calc(-1 * var(--outer-inset, 0px));
|
|
223
387
|
top: calc(-1 * var(--outer-inset, 0px));
|
|
@@ -226,26 +390,46 @@
|
|
|
226
390
|
overflow: clip;
|
|
227
391
|
overflow-clip-margin: border-box;
|
|
228
392
|
contain: layout;
|
|
229
|
-
--
|
|
393
|
+
--layer-inset: 0px;
|
|
394
|
+
/* A third of the shield's, as cqw is the shield's width: an eighth of the canton's own. */
|
|
395
|
+
--bordure-width: calc(100cqw / 24);
|
|
230
396
|
}
|
|
231
397
|
|
|
232
398
|
/* ---------------------------------------------------------------------------
|
|
233
399
|
* The shield itself
|
|
234
400
|
* ------------------------------------------------------------------------- */
|
|
235
401
|
|
|
236
|
-
/*
|
|
237
|
-
|
|
402
|
+
/* Heater: a pointed shield round its one child, the field, which carries the blazon. The
|
|
403
|
+
* outline and the clip are in ordinaries.js, from drawings/heater.svg. */
|
|
404
|
+
@utility heater {
|
|
238
405
|
position: relative;
|
|
406
|
+
display: grid;
|
|
239
407
|
aspect-ratio: 5 / 6;
|
|
240
|
-
/* At the border's edge, so a canton or chief can surmount the bordure. */
|
|
241
408
|
overflow: clip;
|
|
242
|
-
overflow-clip-margin: border-box;
|
|
243
|
-
border-radius: 0 0 50% 50% / 0 0 45% 45%;
|
|
244
|
-
box-shadow: 0 0 0 2px var(--color-sable);
|
|
245
409
|
/* The containing block for counterchanged charges' fixed layers. */
|
|
246
410
|
contain: layout;
|
|
247
|
-
|
|
248
|
-
|
|
411
|
+
container-type: inline-size;
|
|
412
|
+
/* An eighth of the shield's width (BORDURE_WIDTH in ordinaries.js). */
|
|
413
|
+
--bordure-width: calc(100cqw / 8);
|
|
414
|
+
|
|
415
|
+
/* The field. A canton steps out over its bordure, and a counterchanged charge's fixed layer
|
|
416
|
+
* in by it, to the field's padding box. */
|
|
417
|
+
& > * {
|
|
418
|
+
position: relative;
|
|
419
|
+
--the-first: var(--field-first);
|
|
420
|
+
--outer-inset: var(--bordure-inset);
|
|
421
|
+
--layer-inset: var(--bordure-inset);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* ---------------------------------------------------------------------------
|
|
426
|
+
* Fur density
|
|
427
|
+
* ------------------------------------------------------------------------- */
|
|
428
|
+
|
|
429
|
+
/* Whole fur tiles across the field, e.g. `fur-3` for beffroi or `fur-6` for menu-vair. Ermine
|
|
430
|
+
* defaults to 6 and vair to 4. */
|
|
431
|
+
@utility fur-* {
|
|
432
|
+
--fur-across: --value(integer);
|
|
249
433
|
}
|
|
250
434
|
|
|
251
435
|
/* ---------------------------------------------------------------------------
|