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/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
|
@@ -78,19 +78,37 @@
|
|
|
78
78
|
syntax: "*";
|
|
79
79
|
inherits: false;
|
|
80
80
|
}
|
|
81
|
-
/* A field's first tincture. The
|
|
81
|
+
/* A field's first tincture. The heater's field copies it to --the-first, which inherits,
|
|
82
82
|
* so a canton's own field can't change it. */
|
|
83
83
|
@property --field-first {
|
|
84
84
|
syntax: "*";
|
|
85
85
|
inherits: false;
|
|
86
86
|
}
|
|
87
|
-
/*
|
|
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
|
|
88
95
|
* inherits, so a canton can step out over the shield's bordure and not its own. */
|
|
89
96
|
@property --bordure-inset {
|
|
90
97
|
syntax: "<length>";
|
|
91
98
|
inherits: false;
|
|
92
99
|
initial-value: 0px;
|
|
93
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
|
+
}
|
|
94
112
|
|
|
95
113
|
/* ---------------------------------------------------------------------------
|
|
96
114
|
* Tinctures
|
|
@@ -117,30 +135,51 @@
|
|
|
117
135
|
@utility or {
|
|
118
136
|
background-color: var(--color-or);
|
|
119
137
|
--field-first: var(--color-or);
|
|
138
|
+
.heater:has(> &) {
|
|
139
|
+
--frame-first: var(--color-or);
|
|
140
|
+
}
|
|
120
141
|
}
|
|
121
142
|
@utility argent {
|
|
122
143
|
background-color: var(--color-argent);
|
|
123
144
|
--field-first: var(--color-argent);
|
|
145
|
+
.heater:has(> &) {
|
|
146
|
+
--frame-first: var(--color-argent);
|
|
147
|
+
}
|
|
124
148
|
}
|
|
125
149
|
@utility gules {
|
|
126
150
|
background-color: var(--color-gules);
|
|
127
151
|
--field-first: var(--color-gules);
|
|
152
|
+
.heater:has(> &) {
|
|
153
|
+
--frame-first: var(--color-gules);
|
|
154
|
+
}
|
|
128
155
|
}
|
|
129
156
|
@utility azure {
|
|
130
157
|
background-color: var(--color-azure);
|
|
131
158
|
--field-first: var(--color-azure);
|
|
159
|
+
.heater:has(> &) {
|
|
160
|
+
--frame-first: var(--color-azure);
|
|
161
|
+
}
|
|
132
162
|
}
|
|
133
163
|
@utility vert {
|
|
134
164
|
background-color: var(--color-vert);
|
|
135
165
|
--field-first: var(--color-vert);
|
|
166
|
+
.heater:has(> &) {
|
|
167
|
+
--frame-first: var(--color-vert);
|
|
168
|
+
}
|
|
136
169
|
}
|
|
137
170
|
@utility purpure {
|
|
138
171
|
background-color: var(--color-purpure);
|
|
139
172
|
--field-first: var(--color-purpure);
|
|
173
|
+
.heater:has(> &) {
|
|
174
|
+
--frame-first: var(--color-purpure);
|
|
175
|
+
}
|
|
140
176
|
}
|
|
141
177
|
@utility sable {
|
|
142
178
|
background-color: var(--color-sable);
|
|
143
179
|
--field-first: var(--color-sable);
|
|
180
|
+
.heater:has(> &) {
|
|
181
|
+
--frame-first: var(--color-sable);
|
|
182
|
+
}
|
|
144
183
|
}
|
|
145
184
|
/* The field's first tincture, for charges and for a canton or inescutcheon field. */
|
|
146
185
|
@utility of-the-first {
|
|
@@ -151,7 +190,9 @@
|
|
|
151
190
|
* Field divisions
|
|
152
191
|
* ------------------------------------------------------------------------- */
|
|
153
192
|
|
|
154
|
-
/* 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. */
|
|
155
196
|
@utility per-pale {
|
|
156
197
|
display: grid;
|
|
157
198
|
grid-auto-flow: column;
|
|
@@ -159,9 +200,10 @@
|
|
|
159
200
|
|
|
160
201
|
/* On a shield, each part is a coat of its own: what's in it is placed, stacked and
|
|
161
202
|
* counterchanged within it. */
|
|
162
|
-
&:is(.
|
|
203
|
+
&:is(.heater, .heater *) > :not(.charges, .inescutcheon, .canton) {
|
|
163
204
|
position: relative;
|
|
164
205
|
contain: layout;
|
|
206
|
+
--layer-inset: 0px;
|
|
165
207
|
}
|
|
166
208
|
}
|
|
167
209
|
|
|
@@ -173,15 +215,20 @@
|
|
|
173
215
|
|
|
174
216
|
/* On a shield, two parts split at the fess point, FESS_POINT in divisions.js.
|
|
175
217
|
* More parts stay equal. */
|
|
176
|
-
&:is(.
|
|
177
|
-
&: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
|
+
) {
|
|
178
224
|
grid-template-rows: 45fr 55fr;
|
|
179
225
|
}
|
|
180
226
|
|
|
181
227
|
/* See per-pale. */
|
|
182
|
-
& >
|
|
228
|
+
& > :not(.charges, .inescutcheon, .canton) {
|
|
183
229
|
position: relative;
|
|
184
230
|
contain: layout;
|
|
231
|
+
--layer-inset: 0px;
|
|
185
232
|
}
|
|
186
233
|
}
|
|
187
234
|
}
|
|
@@ -194,19 +241,20 @@
|
|
|
194
241
|
grid-template-rows: 1fr 1fr;
|
|
195
242
|
|
|
196
243
|
/* Each quarter is a coat of its own, so it starts its own count. */
|
|
197
|
-
& >
|
|
244
|
+
& > :not(.charges, .inescutcheon, .canton) {
|
|
198
245
|
--the-first: var(--field-first);
|
|
199
246
|
--outer-inset: 0px;
|
|
200
247
|
}
|
|
201
248
|
|
|
202
249
|
/* On a shield, split at the fess point, as per-fess. */
|
|
203
|
-
&:is(.
|
|
250
|
+
&:is(.heater, .heater *) {
|
|
204
251
|
grid-template-rows: 45fr 55fr;
|
|
205
252
|
|
|
206
253
|
/* See per-pale. */
|
|
207
|
-
& >
|
|
254
|
+
& > :not(.charges, .inescutcheon, .canton) {
|
|
208
255
|
position: relative;
|
|
209
256
|
contain: layout;
|
|
257
|
+
--layer-inset: 0px;
|
|
210
258
|
}
|
|
211
259
|
}
|
|
212
260
|
}
|
|
@@ -215,37 +263,117 @@
|
|
|
215
263
|
* Subordinaries
|
|
216
264
|
* ------------------------------------------------------------------------- */
|
|
217
265
|
|
|
218
|
-
/* 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. */
|
|
219
271
|
@utility bordure-* {
|
|
220
272
|
border-style: solid;
|
|
221
273
|
border-width: var(--bordure-width, 0.75rem);
|
|
222
274
|
border-color: --value(--color-*);
|
|
223
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
|
+
}
|
|
224
311
|
}
|
|
225
312
|
@utility bordure-of-the-first {
|
|
226
313
|
border-style: solid;
|
|
227
314
|
border-width: var(--bordure-width, 0.75rem);
|
|
228
315
|
border-color: var(--the-first, transparent);
|
|
229
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
|
+
}
|
|
230
352
|
}
|
|
231
353
|
|
|
232
|
-
/* Inescutcheon: a
|
|
233
|
-
* 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. */
|
|
234
357
|
@utility inescutcheon {
|
|
235
358
|
position: absolute;
|
|
236
|
-
z-index:
|
|
359
|
+
z-index: 5;
|
|
237
360
|
width: 33%;
|
|
238
361
|
left: 33.5%;
|
|
239
362
|
top: 28.5%;
|
|
363
|
+
display: grid;
|
|
240
364
|
aspect-ratio: 5 / 6;
|
|
241
365
|
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
366
|
contain: layout;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
--
|
|
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
|
+
}
|
|
249
377
|
}
|
|
250
378
|
|
|
251
379
|
/* Canton: a third of the shield each way in dexter chief, so a ninth of it, and
|
|
@@ -253,7 +381,7 @@
|
|
|
253
381
|
* ordinary and charge, as a later addition to the arms, and over the bordure. */
|
|
254
382
|
@utility canton {
|
|
255
383
|
position: absolute;
|
|
256
|
-
z-index:
|
|
384
|
+
z-index: 5;
|
|
257
385
|
width: calc((100% + 2 * var(--outer-inset, 0px)) / 3);
|
|
258
386
|
left: calc(-1 * var(--outer-inset, 0px));
|
|
259
387
|
top: calc(-1 * var(--outer-inset, 0px));
|
|
@@ -262,26 +390,46 @@
|
|
|
262
390
|
overflow: clip;
|
|
263
391
|
overflow-clip-margin: border-box;
|
|
264
392
|
contain: layout;
|
|
265
|
-
--
|
|
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);
|
|
266
396
|
}
|
|
267
397
|
|
|
268
398
|
/* ---------------------------------------------------------------------------
|
|
269
399
|
* The shield itself
|
|
270
400
|
* ------------------------------------------------------------------------- */
|
|
271
401
|
|
|
272
|
-
/*
|
|
273
|
-
|
|
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 {
|
|
274
405
|
position: relative;
|
|
406
|
+
display: grid;
|
|
275
407
|
aspect-ratio: 5 / 6;
|
|
276
|
-
/* At the border's edge, so a canton or chief can surmount the bordure. */
|
|
277
408
|
overflow: clip;
|
|
278
|
-
overflow-clip-margin: border-box;
|
|
279
|
-
border-radius: 0 0 50% 50% / 0 0 45% 45%;
|
|
280
|
-
box-shadow: 0 0 0 2px var(--color-sable);
|
|
281
409
|
/* The containing block for counterchanged charges' fixed layers. */
|
|
282
410
|
contain: layout;
|
|
283
|
-
|
|
284
|
-
|
|
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);
|
|
285
433
|
}
|
|
286
434
|
|
|
287
435
|
/* ---------------------------------------------------------------------------
|