magick-components 0.0.0-stage → 0.1.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/LICENSE.txt +89 -0
- package/README.md +353 -2
- package/fonts/FrankRuhlLibre-Bold-letters.woff2 +0 -0
- package/fonts/FrankRuhlLibre-OFL.txt +105 -0
- package/fonts/NotoSerif-BoldItalic-compass.woff2 +0 -0
- package/fonts/NotoSerif-Italic-latin1.woff2 +0 -0
- package/fonts/NotoSerif-OFL.txt +93 -0
- package/fonts/SOURCES.md +35 -0
- package/fonts/gd-hall.css +3 -0
- package/lib/astrology/Kamea.d.ts +75 -0
- package/lib/astrology/Kamea.js +73 -0
- package/lib/astrology/MercuryWidget.d.ts +21 -0
- package/lib/astrology/MercuryWidget.js +80 -0
- package/lib/astrology/Moon.d.ts +32 -0
- package/lib/astrology/Moon.js +39 -0
- package/lib/astrology/MoonWidget.d.ts +20 -0
- package/lib/astrology/MoonWidget.js +63 -0
- package/lib/astrology/PlanetarySpirit.d.ts +103 -0
- package/lib/astrology/PlanetarySpirit.js +102 -0
- package/lib/astrology/mercuryRetrograde.d.ts +20 -0
- package/lib/astrology/mercuryRetrograde.js +80 -0
- package/lib/astrology/moonPath.d.ts +20 -0
- package/lib/astrology/moonPath.js +45 -0
- package/lib/chakras/Chakras.d.ts +34 -0
- package/lib/chakras/Chakras.js +149 -0
- package/lib/cssString.d.ts +5 -0
- package/lib/cssString.js +7 -0
- package/lib/drawingVersions.d.ts +29 -0
- package/lib/drawingVersions.js +15 -0
- package/lib/enochian/Tablet.d.ts +17 -0
- package/lib/enochian/Tablet.js +24 -0
- package/lib/enochian/tabletSigils.d.ts +46 -0
- package/lib/enochian/tabletSigils.js +172 -0
- package/lib/enochian/tabletSvg.d.ts +85 -0
- package/lib/enochian/tabletSvg.js +278 -0
- package/lib/gd/FylfotCross.d.ts +60 -0
- package/lib/gd/FylfotCross.js +121 -0
- package/lib/gd/GradeTree.d.ts +10 -0
- package/lib/gd/GradeTree.js +21 -0
- package/lib/gd/Lamen.d.ts +31 -0
- package/lib/gd/Lamen.js +317 -0
- package/lib/gd/RoseSigil.d.ts +31 -0
- package/lib/gd/RoseSigil.js +49 -0
- package/lib/gd/RoseSigilImage.d.ts +38 -0
- package/lib/gd/RoseSigilImage.js +29 -0
- package/lib/gd/SevenBranchedCandlestick.d.ts +54 -0
- package/lib/gd/SevenBranchedCandlestick.js +269 -0
- package/lib/gd/TableOfShewbread.d.ts +19 -0
- package/lib/gd/TableOfShewbread.js +120 -0
- package/lib/gd/hall/GdHall.d.ts +94 -0
- package/lib/gd/hall/GdHall.js +147 -0
- package/lib/gd/hall/art/altar.d.ts +8 -0
- package/lib/gd/hall/art/altar.js +21 -0
- package/lib/gd/hall/art/altarItems.d.ts +20 -0
- package/lib/gd/hall/art/altarItems.js +113 -0
- package/lib/gd/hall/art/banner.d.ts +23 -0
- package/lib/gd/hall/art/banner.js +64 -0
- package/lib/gd/hall/art/board.d.ts +9 -0
- package/lib/gd/hall/art/board.js +49 -0
- package/lib/gd/hall/art/cards.d.ts +11 -0
- package/lib/gd/hall/art/cards.js +124 -0
- package/lib/gd/hall/art/furnishings.d.ts +12 -0
- package/lib/gd/hall/art/furnishings.js +36 -0
- package/lib/gd/hall/art/geometry.d.ts +49 -0
- package/lib/gd/hall/art/geometry.js +97 -0
- package/lib/gd/hall/art/hebrewLetter.d.ts +10 -0
- package/lib/gd/hall/art/hebrewLetter.js +38 -0
- package/lib/gd/hall/art/index.d.ts +7 -0
- package/lib/gd/hall/art/index.js +61 -0
- package/lib/gd/hall/art/items.d.ts +12 -0
- package/lib/gd/hall/art/items.js +125 -0
- package/lib/gd/hall/art/kamea.d.ts +20 -0
- package/lib/gd/hall/art/kamea.js +64 -0
- package/lib/gd/hall/art/lamen.d.ts +9 -0
- package/lib/gd/hall/art/lamen.js +52 -0
- package/lib/gd/hall/art/lightning.d.ts +13 -0
- package/lib/gd/hall/art/lightning.js +44 -0
- package/lib/gd/hall/art/lights.d.ts +13 -0
- package/lib/gd/hall/art/lights.js +64 -0
- package/lib/gd/hall/art/marker.d.ts +8 -0
- package/lib/gd/hall/art/marker.js +19 -0
- package/lib/gd/hall/art/pantacle.d.ts +10 -0
- package/lib/gd/hall/art/pantacle.js +119 -0
- package/lib/gd/hall/art/pillar.d.ts +4 -0
- package/lib/gd/hall/art/pillar.js +25 -0
- package/lib/gd/hall/art/placeholder.d.ts +8 -0
- package/lib/gd/hall/art/placeholder.js +18 -0
- package/lib/gd/hall/art/platform.d.ts +13 -0
- package/lib/gd/hall/art/platform.js +28 -0
- package/lib/gd/hall/art/seals.d.ts +23 -0
- package/lib/gd/hall/art/seals.js +151 -0
- package/lib/gd/hall/art/shared.d.ts +87 -0
- package/lib/gd/hall/art/shared.js +95 -0
- package/lib/gd/hall/art/symbols.d.ts +37 -0
- package/lib/gd/hall/art/symbols.js +134 -0
- package/lib/gd/hall/art/tarotKey.d.ts +34 -0
- package/lib/gd/hall/art/tarotKey.js +101 -0
- package/lib/gd/hall/art/veil.d.ts +8 -0
- package/lib/gd/hall/art/veil.js +22 -0
- package/lib/gd/hall/art/vessels.d.ts +19 -0
- package/lib/gd/hall/art/vessels.js +74 -0
- package/lib/gd/hall/aspect.d.ts +31 -0
- package/lib/gd/hall/aspect.js +161 -0
- package/lib/gd/hall/attributes.d.ts +52 -0
- package/lib/gd/hall/attributes.js +116 -0
- package/lib/gd/hall/describe.d.ts +38 -0
- package/lib/gd/hall/describe.js +184 -0
- package/lib/gd/hall/dimensions.d.ts +39 -0
- package/lib/gd/hall/dimensions.js +49 -0
- package/lib/gd/hall/editor.d.ts +13 -0
- package/lib/gd/hall/editor.js +13 -0
- package/lib/gd/hall/fontMetrics.json +177 -0
- package/lib/gd/hall/fonts.d.ts +45 -0
- package/lib/gd/hall/fonts.js +73 -0
- package/lib/gd/hall/hebrewLetters.d.ts +8 -0
- package/lib/gd/hall/hebrewLetters.js +40 -0
- package/lib/gd/hall/index.d.ts +19 -0
- package/lib/gd/hall/index.js +17 -0
- package/lib/gd/hall/labels.d.ts +72 -0
- package/lib/gd/hall/labels.js +109 -0
- package/lib/gd/hall/layout.d.ts +213 -0
- package/lib/gd/hall/layout.js +837 -0
- package/lib/gd/hall/planets.d.ts +13 -0
- package/lib/gd/hall/planets.js +35 -0
- package/lib/gd/hall/presets/index.d.ts +13 -0
- package/lib/gd/hall/presets/index.js +29 -0
- package/lib/gd/hall/presets/neophyte.d.ts +7 -0
- package/lib/gd/hall/presets/neophyte.js +74 -0
- package/lib/gd/hall/presets/philosophus.d.ts +30 -0
- package/lib/gd/hall/presets/philosophus.js +244 -0
- package/lib/gd/hall/presets/portal.d.ts +24 -0
- package/lib/gd/hall/presets/portal.js +241 -0
- package/lib/gd/hall/presets/practicus.d.ts +17 -0
- package/lib/gd/hall/presets/practicus.js +181 -0
- package/lib/gd/hall/presets/theoricus.d.ts +13 -0
- package/lib/gd/hall/presets/theoricus.js +178 -0
- package/lib/gd/hall/presets/types.d.ts +72 -0
- package/lib/gd/hall/presets/types.js +58 -0
- package/lib/gd/hall/presets/zelator.d.ts +12 -0
- package/lib/gd/hall/presets/zelator.js +161 -0
- package/lib/gd/hall/spec.d.ts +54 -0
- package/lib/gd/hall/spec.js +108 -0
- package/lib/gd/hall/types.d.ts +131 -0
- package/lib/gd/hall/types.js +1 -0
- package/lib/gd/hall/validate.d.ts +13 -0
- package/lib/gd/hall/validate.js +285 -0
- package/lib/gd/hall/vocabulary.d.ts +139 -0
- package/lib/gd/hall/vocabulary.js +435 -0
- package/lib/gd/lamenGlyphs.d.ts +16 -0
- package/lib/gd/lamenGlyphs.js +167 -0
- package/lib/gd/lamenGrades.d.ts +29 -0
- package/lib/gd/lamenGrades.js +36 -0
- package/lib/gd/roseSigilGeometry.d.ts +10 -0
- package/lib/gd/roseSigilGeometry.js +10 -0
- package/lib/gd/roseSigilInternals.d.ts +85 -0
- package/lib/gd/roseSigilInternals.js +343 -0
- package/lib/gd/roseSigilOptimizer.d.ts +15 -0
- package/lib/gd/roseSigilOptimizer.js +59 -0
- package/lib/gd/roseSigilVersions.d.ts +12 -0
- package/lib/gd/roseSigilVersions.js +57 -0
- package/lib/hooks/useHydrated.d.ts +9 -0
- package/lib/hooks/useHydrated.js +13 -0
- package/lib/kabbalah/TreeOfLife.d.ts +135 -0
- package/lib/kabbalah/TreeOfLife.js +312 -0
- package/lib/kabbalah/fieldPath.d.ts +25 -0
- package/lib/kabbalah/fieldPath.js +100 -0
- package/lib/kabbalah/treeLabels.d.ts +30 -0
- package/lib/kabbalah/treeLabels.js +32 -0
- package/lib/kabbalah/treeOfLifeFigure.d.ts +59 -0
- package/lib/kabbalah/treeOfLifeFigure.js +151 -0
- package/lib/svgCoordinate.d.ts +12 -0
- package/lib/svgCoordinate.js +16 -0
- package/package.json +162 -3
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useMemo, } from "react";
|
|
3
|
+
import { svgCoordinate } from "../../svgCoordinate.js";
|
|
4
|
+
import { DEFAULT_HALL_ART } from "./art/index.js";
|
|
5
|
+
import { Placeholder } from "./art/placeholder.js";
|
|
6
|
+
import { PALETTE } from "./art/shared.js";
|
|
7
|
+
import { GD_HALL_FONT_STACKS, gdHallFontFaceCss } from "./fonts.js";
|
|
8
|
+
import { FLOOR_SIZE, FLOOR_STROKE, resolveHall, WALL_GAP, WALL_STROKE, } from "./layout.js";
|
|
9
|
+
/** SVG user units per checker square: text stays at a large font size. */
|
|
10
|
+
const UNIT = 100;
|
|
11
|
+
const u = (squares) => svgCoordinate(squares * UNIT);
|
|
12
|
+
const EMPTY_HALL = { type: "gdHall" };
|
|
13
|
+
const labelTextStyle = {
|
|
14
|
+
fontVariantLigatures: "none",
|
|
15
|
+
fontKerning: "normal",
|
|
16
|
+
};
|
|
17
|
+
function Lines({ label }) {
|
|
18
|
+
return (_jsx(_Fragment, { children: label.lines.map((line, index) => (_jsx("text", { x: u(line.x), y: u(line.y), fontSize: u(label.size), textAnchor: label.anchor, children: line.text }, index))) }));
|
|
19
|
+
}
|
|
20
|
+
function Chrome({ hall, checker, gradient, }) {
|
|
21
|
+
const { outer } = hall;
|
|
22
|
+
// With no dais band (dais=0) the East wall stands at the floor's edge.
|
|
23
|
+
const daisBand = outer.y0 < -WALL_GAP;
|
|
24
|
+
const doorBands = hall.doors.map((door) => {
|
|
25
|
+
// Cover the floor outline, the gap and the wall line on that side; on
|
|
26
|
+
// the East, behind a dais, only the wall line.
|
|
27
|
+
const pad = WALL_STROKE;
|
|
28
|
+
const across = door.side === "west"
|
|
29
|
+
? [FLOOR_SIZE - pad, outer.y1 + pad]
|
|
30
|
+
: door.side === "east"
|
|
31
|
+
? [outer.y0 - pad, daisBand ? outer.y0 + pad : pad]
|
|
32
|
+
: door.side === "north"
|
|
33
|
+
? [outer.x0 - pad, pad]
|
|
34
|
+
: [FLOOR_SIZE - pad, outer.x1 + pad];
|
|
35
|
+
const vertical = door.side === "north" || door.side === "south";
|
|
36
|
+
return vertical
|
|
37
|
+
? { x0: across[0], x1: across[1], y0: door.from, y1: door.to }
|
|
38
|
+
: { x0: door.from, x1: door.to, y0: across[0], y1: across[1] };
|
|
39
|
+
});
|
|
40
|
+
return (_jsxs("g", { children: [_jsx("rect", { x: u(outer.x0), y: u(outer.y0), width: u(outer.x1 - outer.x0), height: u(outer.y1 - outer.y0), fill: hall.wall === "gradient" ? `url(#${gradient})` : PALETTE.paper, stroke: PALETTE.ink, strokeWidth: u(WALL_STROKE) }), _jsx("rect", { x: 0, y: 0, width: u(FLOOR_SIZE), height: u(FLOOR_SIZE), fill: `url(#${checker})`, stroke: PALETTE.ink, strokeWidth: u(FLOOR_STROKE) }), doorBands.map((band, index) => (_jsx("rect", { x: u(band.x0), y: u(band.y0), width: u(band.x1 - band.x0), height: u(band.y1 - band.y0), fill: PALETTE.paper }, index)))] }));
|
|
41
|
+
}
|
|
42
|
+
/** Text drawn over an object's art, in the labels' face. */
|
|
43
|
+
function Inner({ label }) {
|
|
44
|
+
return (_jsx("g", { fontFamily: GD_HALL_FONT_STACKS.serif, fontStyle: "italic", fill: PALETTE.ink, style: labelTextStyle, children: _jsx(Lines, { label: label }) }));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The hall's distinct drawings and which one each object uses. Objects
|
|
48
|
+
* whose art, box and variant agree share one drawing, placed with `<use>`,
|
|
49
|
+
* so a drawing's size counts once per hall however many nodes use it
|
|
50
|
+
* (plan 061, decision 12). Ids follow the objects' order, so they are the
|
|
51
|
+
* same on the server and the client.
|
|
52
|
+
*/
|
|
53
|
+
function artDefinitions(objects, art, id) {
|
|
54
|
+
const definitions = new Map();
|
|
55
|
+
const uses = new Map();
|
|
56
|
+
for (const object of objects) {
|
|
57
|
+
if (object.text)
|
|
58
|
+
continue;
|
|
59
|
+
const drawn = object.missing ? undefined : art.get(object.artKey);
|
|
60
|
+
const key = drawn
|
|
61
|
+
? JSON.stringify([
|
|
62
|
+
"art",
|
|
63
|
+
object.artKey,
|
|
64
|
+
object.box.width,
|
|
65
|
+
object.box.height,
|
|
66
|
+
drawn.variant(object.node),
|
|
67
|
+
])
|
|
68
|
+
: JSON.stringify(["placeholder", object.artKey]);
|
|
69
|
+
let definition = definitions.get(key);
|
|
70
|
+
if (!definition) {
|
|
71
|
+
definition = { id: `${id}-art${definitions.size}`, object, art: drawn };
|
|
72
|
+
definitions.set(key, definition);
|
|
73
|
+
}
|
|
74
|
+
uses.set(object, definition.id);
|
|
75
|
+
}
|
|
76
|
+
return { definitions: [...definitions.values()], uses };
|
|
77
|
+
}
|
|
78
|
+
/** A drawing in `<defs>`, centred on the origin in its art units. */
|
|
79
|
+
function ArtDefinitionGroup({ definition }) {
|
|
80
|
+
const { id, object, art } = definition;
|
|
81
|
+
return (_jsx("g", { id: id, children: art ? (_jsx(art.Component, { node: object.node, box: object.box, defId: (name) => `${id}-${name}` })) : (_jsx(Placeholder, { name: object.artKey })) }));
|
|
82
|
+
}
|
|
83
|
+
/** An object: its drawing placed and scaled, and any text over it. */
|
|
84
|
+
function HallObject({ object, use, }) {
|
|
85
|
+
if (object.text || use === undefined)
|
|
86
|
+
return null;
|
|
87
|
+
return (_jsxs(_Fragment, { children: [_jsx("use", { href: `#${use}`, transform: `translate(${u(object.cx)} ${u(object.cy)}) scale(${svgCoordinate(object.k * UNIT)})` }), object.inner && _jsx(Inner, { label: object.inner })] }));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The hall a node draws: itself when it is a `gdHall`, an empty hall for
|
|
91
|
+
* anything else.
|
|
92
|
+
*/
|
|
93
|
+
export function hallRoot(hall) {
|
|
94
|
+
return typeof hall === "object" && hall !== null && hall.type === "gdHall"
|
|
95
|
+
? hall
|
|
96
|
+
: EMPTY_HALL;
|
|
97
|
+
}
|
|
98
|
+
/** A resolved viewBox in SVG units, as `GdHallSvg` writes it. */
|
|
99
|
+
export function hallViewBox(resolved, image = false) {
|
|
100
|
+
const { viewBox } = resolved;
|
|
101
|
+
if (!image)
|
|
102
|
+
return [
|
|
103
|
+
u(viewBox.x0),
|
|
104
|
+
u(viewBox.y0),
|
|
105
|
+
u(viewBox.x1 - viewBox.x0),
|
|
106
|
+
u(viewBox.y1 - viewBox.y0),
|
|
107
|
+
];
|
|
108
|
+
const x0 = Math.floor(u(viewBox.x0));
|
|
109
|
+
const y0 = Math.floor(u(viewBox.y0));
|
|
110
|
+
return [x0, y0, Math.ceil(u(viewBox.x1)) - x0, Math.ceil(u(viewBox.y1)) - y0];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The drawing of a resolved hall, without hooks, so a server-side renderer
|
|
114
|
+
* with no hook dispatcher can render it. `GdHall` draws through it.
|
|
115
|
+
*/
|
|
116
|
+
export function GdHallSvg({ resolved, art, idPrefix: id, drawings, fontCss, image = false, className, style, width, height, ref, }) {
|
|
117
|
+
const { definitions, uses } = drawings ?? artDefinitions(resolved.objects, art, id);
|
|
118
|
+
const box = hallViewBox(resolved, image);
|
|
119
|
+
const titleId = `${id}-title`;
|
|
120
|
+
const checker = `${id}-checker`;
|
|
121
|
+
const gradient = `${id}-wall`;
|
|
122
|
+
return (_jsxs("svg", { ref: ref, xmlns: "http://www.w3.org/2000/svg", viewBox: box.join(" "), role: image ? undefined : "img", "aria-labelledby": image ? undefined : titleId, className: className, style: style, width: width, height: height, children: [!image && (_jsx("title", { id: titleId, children: resolved.captionText ?? "Temple hall" })), fontCss && _jsx("style", { children: fontCss }), _jsxs("defs", { children: [_jsxs("pattern", { id: checker, width: 2 * UNIT, height: 2 * UNIT, patternUnits: "userSpaceOnUse", children: [_jsx("rect", { width: 2 * UNIT, height: 2 * UNIT, fill: PALETTE.checkerLight }), _jsx("rect", { width: UNIT, height: UNIT, fill: PALETTE.checkerDark }), _jsx("rect", { x: UNIT, y: UNIT, width: UNIT, height: UNIT, fill: PALETTE.checkerDark })] }), resolved.wall === "gradient" && (_jsxs("linearGradient", { id: gradient, x1: 0, x2: 0, y1: 0, y2: 1, children: [_jsx("stop", { offset: 0.13, stopColor: PALETTE.paper }), _jsx("stop", { offset: 1, stopColor: PALETTE.ink })] })), definitions.map((definition) => (_jsx(ArtDefinitionGroup, { definition: definition }, definition.id)))] }), _jsx("rect", { x: box[0], y: box[1], width: box[2], height: box[3], fill: PALETTE.paper }), _jsx(Chrome, { hall: resolved, checker: checker, gradient: gradient }), resolved.objects.map((object) => (_jsx(HallObject, { object: object, use: uses.get(object) }, object.path.join(".")))), _jsxs("g", { fontFamily: GD_HALL_FONT_STACKS.serif, fontStyle: "italic", fill: PALETTE.ink, style: labelTextStyle, children: [resolved.objects
|
|
123
|
+
.filter((object) => object.text || object.labels.length > 0)
|
|
124
|
+
.map((object) => (_jsxs("g", { children: [object.text && _jsx(Lines, { label: object.text }), object.labels.map((label, index) => (_jsx(Lines, { label: label }, index)))] }, object.path.join(".")))), resolved.compass.map((label) => (_jsx("text", { x: u(label.x), y: u(label.y), fontSize: u(label.size), fontWeight: 700, textAnchor: "middle", transform: label.rotate === 0
|
|
125
|
+
? undefined
|
|
126
|
+
: `rotate(${label.rotate} ${u(label.x)} ${u(label.y)})`, children: label.text }, label.text))), resolved.caption && _jsx(Lines, { label: resolved.caption })] })] }));
|
|
127
|
+
}
|
|
128
|
+
// Plan 061 in the magick.ly repository designs the hall and its renderer.
|
|
129
|
+
/**
|
|
130
|
+
* A Golden Dawn temple hall as inline SVG, drawn from a `gdHall` node such
|
|
131
|
+
* as a preset's (`magick-components/gd/hall/presets`). It renders on the
|
|
132
|
+
* server as well as in the browser: its layout is computed from the node,
|
|
133
|
+
* its labels' widths from the metrics of the fonts the package ships, and
|
|
134
|
+
* its gradient and pattern ids from `useId`, so several halls can share a
|
|
135
|
+
* page. The node is treated as untrusted: its strings only reach text
|
|
136
|
+
* nodes, unknown tags draw as dashed placeholders, and nothing is loaded
|
|
137
|
+
* by URL except the fonts `fontBaseUrl` points at.
|
|
138
|
+
*/
|
|
139
|
+
export function GdHall({ hall, art = DEFAULT_HALL_ART, fontBaseUrl, className, style, width, height, ref, }) {
|
|
140
|
+
const id = useId().replace(/[^A-Za-z0-9_-]/g, "_");
|
|
141
|
+
const root = hallRoot(hall);
|
|
142
|
+
const resolved = useMemo(() => resolveHall(root, { art }), [root, art]);
|
|
143
|
+
const drawings = useMemo(() => artDefinitions(resolved.objects, art, id), [resolved, art, id]);
|
|
144
|
+
const fontCss = useMemo(() => fontBaseUrl === undefined ? undefined : gdHallFontFaceCss(fontBaseUrl), [fontBaseUrl]);
|
|
145
|
+
return (_jsx(GdHallSvg, { resolved: resolved, art: art, idPrefix: id, drawings: drawings, fontCss: fontCss, className: className, style: style, width: width, height: height, ref: ref }));
|
|
146
|
+
}
|
|
147
|
+
export default GdHall;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HallArt } from "../types.ts";
|
|
2
|
+
export declare const provenance: import("../types.ts").HallArtProvenance;
|
|
3
|
+
/**
|
|
4
|
+
* The altar: a black 2 × 2-square top, or a white one (`kind="white"`,
|
|
5
|
+
* the Portal's small white altar, sized by `scale`), and a host for what
|
|
6
|
+
* lies on it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const altarArt: HallArt;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { enumAttr } from "../attributes.js";
|
|
3
|
+
import { ALTAR_KINDS } from "../vocabulary.js";
|
|
4
|
+
import { original, PALETTE, variantOf } from "./shared.js";
|
|
5
|
+
export const provenance = original("The altar seen from above: the black double cube as a black square, as " +
|
|
6
|
+
"in the drawings, or the small white altar as a white one.");
|
|
7
|
+
const kindOf = (node) => enumAttr(node, "kind", ALTAR_KINDS) ?? "black";
|
|
8
|
+
function Altar({ node }) {
|
|
9
|
+
return kindOf(node) === "white" ? (_jsx("rect", { x: -48, y: -48, width: 96, height: 96, fill: PALETTE.paper, stroke: PALETTE.ink, strokeWidth: 4 })) : (_jsx("rect", { x: -50, y: -50, width: 100, height: 100, fill: PALETTE.ink }));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The altar: a black 2 × 2-square top, or a white one (`kind="white"`,
|
|
13
|
+
* the Portal's small white altar, sized by `scale`), and a host for what
|
|
14
|
+
* lies on it.
|
|
15
|
+
*/
|
|
16
|
+
export const altarArt = {
|
|
17
|
+
Component: Altar,
|
|
18
|
+
box: { width: 100, height: 100 },
|
|
19
|
+
variant: (node) => variantOf(kindOf(node)),
|
|
20
|
+
provenance,
|
|
21
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HallArt } from "../types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The rose, the salt, the bread and the fan (plan 061, stage H): flat
|
|
4
|
+
* emblems in the hall's palette with the items' one outline, drawn into
|
|
5
|
+
* the boxes the old drawings' items had (decision 4).
|
|
6
|
+
*/
|
|
7
|
+
export declare const roseProvenance: import("../types.ts").HallArtProvenance;
|
|
8
|
+
export declare const saltProvenance: import("../types.ts").HallArtProvenance;
|
|
9
|
+
export declare const breadProvenance: import("../types.ts").HallArtProvenance;
|
|
10
|
+
export declare const fanProvenance: import("../types.ts").HallArtProvenance;
|
|
11
|
+
/** Every art module exports its provenance; these share a module. */
|
|
12
|
+
export declare const provenance: import("../types.ts").HallArtProvenance[];
|
|
13
|
+
/** The rose (`item(kind="rose")`), laid East–West, 100 × 36.35. */
|
|
14
|
+
export declare const roseArt: HallArt;
|
|
15
|
+
/** The salt (`item(kind="salt")`): a shaker, 42.41 × 100. */
|
|
16
|
+
export declare const saltArt: HallArt;
|
|
17
|
+
/** The bread (`item(kind="bread")`): a round loaf, 100 × 100. */
|
|
18
|
+
export declare const breadArt: HallArt;
|
|
19
|
+
/** The fan (`item(kind="fan")`), open, 100 × 62.79. */
|
|
20
|
+
export declare const fanArt: HallArt;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { mirrored, pathPoint, polar } from "./geometry.js";
|
|
3
|
+
import { HIGHLIGHT, ITEM_DETAIL, ITEM_OUTLINE, original, PALETTE, sameForEveryNode, } from "./shared.js";
|
|
4
|
+
/**
|
|
5
|
+
* The rose, the salt, the bread and the fan (plan 061, stage H): flat
|
|
6
|
+
* emblems in the hall's palette with the items' one outline, drawn into
|
|
7
|
+
* the boxes the old drawings' items had (decision 4).
|
|
8
|
+
*/
|
|
9
|
+
export const roseProvenance = original("A heraldic rose laid down: five petals over five, the barbs between " +
|
|
10
|
+
"them and the seed, on a stem with two leaves and two thorns, drawn " +
|
|
11
|
+
"from circles and curves.");
|
|
12
|
+
export const saltProvenance = original("A salt shaker drawn from geometry: a pierced silver cap on a glass of " +
|
|
13
|
+
"salt marked with salt's alchemical sign.");
|
|
14
|
+
export const breadProvenance = original("A round loaf drawn from curves, its crust scored three times.");
|
|
15
|
+
export const fanProvenance = original("A folding fan in Air's yellow, drawn from geometry: twelve ribs about " +
|
|
16
|
+
"a rivet, the leaf scalloped between them.");
|
|
17
|
+
/** Every art module exports its provenance; these share a module. */
|
|
18
|
+
export const provenance = [
|
|
19
|
+
roseProvenance,
|
|
20
|
+
saltProvenance,
|
|
21
|
+
breadProvenance,
|
|
22
|
+
fanProvenance,
|
|
23
|
+
];
|
|
24
|
+
const p = pathPoint;
|
|
25
|
+
const PETALS = [0, 1, 2, 3, 4];
|
|
26
|
+
/** The rose's head: centred on the stem's East end, radius 15.5. */
|
|
27
|
+
const HEAD = [-31.5, 0];
|
|
28
|
+
const K = 15.5 / 18;
|
|
29
|
+
/** The petals, outer then inner, as [x, y, r]. */
|
|
30
|
+
const ROSE_PETALS = [
|
|
31
|
+
...PETALS.map((i) => [...polar(9 * K, -90 + i * 72, HEAD), 8.6 * K]),
|
|
32
|
+
...PETALS.map((i) => [...polar(4.6 * K, -54 + i * 72, HEAD), 5.6 * K]),
|
|
33
|
+
].map((values) => values.map((value) => Math.round(value * 100) / 100));
|
|
34
|
+
/** The barbs between the outer petals, as one path. */
|
|
35
|
+
const ROSE_BARBS = PETALS.map((i) => {
|
|
36
|
+
const angle = -54 + i * 72;
|
|
37
|
+
const tip = polar(19.5 * K, angle, HEAD);
|
|
38
|
+
return `M${p(polar(12 * K, angle - 14, HEAD))}L${p(tip)}L${p(polar(12 * K, angle + 14, HEAD))}Z`;
|
|
39
|
+
}).join("");
|
|
40
|
+
/** A leaf, two quadratic curves from `base` out along `angle`. */
|
|
41
|
+
function leaf(base, angle, length) {
|
|
42
|
+
const middle = polar(length * 0.45, angle, base);
|
|
43
|
+
const side = (turn) => p(polar(length * 0.36, angle + turn, middle));
|
|
44
|
+
const tip = p(polar(length, angle, base));
|
|
45
|
+
return `M${p(base)}Q${side(-90)} ${tip}Q${side(90)} ${p(base)}Z`;
|
|
46
|
+
}
|
|
47
|
+
const ROSE_STEM = "M-16,1C0,5 20,-4 46,2";
|
|
48
|
+
const ROSE_LEAVES = leaf([6, 2.6], 60, 13) + leaf([24, -0.3], -55, 13);
|
|
49
|
+
const ROSE_THORNS = "M9.5,2.3L12,6.8L14.5,2.3ZM29.5,-0.2L32,-4.7L34.5,-0.2Z";
|
|
50
|
+
/** The rose (`item(kind="rose")`), laid East–West, 100 × 36.35. */
|
|
51
|
+
export const roseArt = {
|
|
52
|
+
Component: () => (_jsxs("g", { ...ITEM_OUTLINE, strokeWidth: ITEM_DETAIL, children: [_jsx("path", { d: ROSE_STEM, fill: "none", strokeWidth: 7 }), _jsx("path", { d: ROSE_STEM, fill: "none", stroke: PALETTE.green, strokeWidth: 3.4 }), _jsx("path", { d: ROSE_THORNS, fill: PALETTE.green, strokeWidth: 1.2 }), _jsx("path", { d: ROSE_LEAVES, fill: PALETTE.green }), _jsx("path", { d: ROSE_BARBS, fill: PALETTE.green, strokeWidth: 1.6 }), ROSE_PETALS.map(([cx, cy, r]) => (_jsx("circle", { cx: cx, cy: cy, r: r, fill: PALETTE.rose }, `${cx},${cy}`))), _jsx("circle", { cx: HEAD[0], cy: HEAD[1], r: 3.1, fill: PALETTE.gold, strokeWidth: 1.6 })] })),
|
|
53
|
+
box: { width: 100, height: 36.35 },
|
|
54
|
+
variant: sameForEveryNode,
|
|
55
|
+
provenance: roseProvenance,
|
|
56
|
+
};
|
|
57
|
+
const SALT_GLASS = mirrored([0, -27], [
|
|
58
|
+
{ to: [15, -27] },
|
|
59
|
+
{ c1: [19, -16], c2: [19, 30], to: [17, 44] },
|
|
60
|
+
{ q: [17, 48], to: [13, 48] },
|
|
61
|
+
{ to: [0, 48] },
|
|
62
|
+
]);
|
|
63
|
+
const SALT_CAP = mirrored([0, -48], [
|
|
64
|
+
{ c1: [10, -48], c2: [16, -42], to: [16, -34] },
|
|
65
|
+
{ to: [16, -27] },
|
|
66
|
+
{ to: [0, -27] },
|
|
67
|
+
]);
|
|
68
|
+
const SALT_HOLES = "M-6,-40h0M0,-42h0M6,-40h0";
|
|
69
|
+
/** The salt (`item(kind="salt")`): a shaker, 42.41 × 100. */
|
|
70
|
+
export const saltArt = {
|
|
71
|
+
Component: () => (_jsxs("g", { ...ITEM_OUTLINE, children: [_jsx("path", { d: SALT_GLASS, fill: PALETTE.paper }), _jsx("path", { d: SALT_CAP, fill: PALETTE.silver }), _jsx("path", { d: SALT_HOLES, strokeWidth: 3.4 }), _jsx("path", { d: "M-16,-30.5H16", strokeWidth: ITEM_DETAIL }), _jsx("circle", { cy: 12, r: 8, fill: "none", strokeWidth: ITEM_DETAIL }), _jsx("path", { d: "M-8,12H8", strokeWidth: ITEM_DETAIL }), _jsx("path", { d: "M-11,-38C-9,-42.5 -6,-44.5 -2,-45", ...HIGHLIGHT })] })),
|
|
72
|
+
box: { width: 42.41, height: 100 },
|
|
73
|
+
variant: sameForEveryNode,
|
|
74
|
+
provenance: saltProvenance,
|
|
75
|
+
};
|
|
76
|
+
const LOAF = "M-44,26C-50,-10 -28,-38 0,-38C28,-38 50,-10 44,26C32,40 -32,40 -44,26Z";
|
|
77
|
+
/** Three slashes across the crust, each a lens the bake opened. */
|
|
78
|
+
const SLASHES = [-22, 0, 22]
|
|
79
|
+
.map((x) => `M${x - 11},10Q${x - 1},-12 ${x + 11},-16Q${x + 1},-1 ${x - 11},10Z`)
|
|
80
|
+
.join("");
|
|
81
|
+
/** The bread (`item(kind="bread")`): a round loaf, 100 × 100. */
|
|
82
|
+
export const breadArt = {
|
|
83
|
+
Component: () => (_jsxs("g", { ...ITEM_OUTLINE, children: [_jsx("path", { d: LOAF, fill: PALETTE.bread }), _jsx("path", { d: SLASHES, fill: PALETTE.crumb, strokeWidth: ITEM_DETAIL })] })),
|
|
84
|
+
box: { width: 100, height: 100 },
|
|
85
|
+
variant: sameForEveryNode,
|
|
86
|
+
provenance: breadProvenance,
|
|
87
|
+
};
|
|
88
|
+
const PIVOT = [0, 24.5];
|
|
89
|
+
const OUTER = 51;
|
|
90
|
+
const INNER = 16;
|
|
91
|
+
const FIRST = -150;
|
|
92
|
+
const LAST = -30;
|
|
93
|
+
const RIBS = 12;
|
|
94
|
+
const ANGLES = Array.from({ length: RIBS + 1 }, (_, i) => FIRST + ((LAST - FIRST) * i) / RIBS);
|
|
95
|
+
const at = (r, angle) => p(polar(r, angle, PIVOT));
|
|
96
|
+
/** The leaf: out along the first rib, scalloped between the ribs, back. */
|
|
97
|
+
const FAN_LEAF = `M${at(INNER, FIRST)}L${at(OUTER, FIRST)}${ANGLES.slice(1)
|
|
98
|
+
.map((angle) => `Q${at(OUTER + 5.5, angle - (LAST - FIRST) / RIBS / 2)} ${at(OUTER, angle)}`)
|
|
99
|
+
.join("")}L${at(INNER, LAST)}A${INNER} ${INNER} 0 0 0 ${at(INNER, FIRST)}Z`;
|
|
100
|
+
const FAN_RIBS = ANGLES.slice(1, -1)
|
|
101
|
+
.map((angle) => `M${at(INNER, angle)}L${at(OUTER - 1, angle)}`)
|
|
102
|
+
.join("");
|
|
103
|
+
const FAN_STICKS = ANGLES.map((angle) => `M${p(PIVOT)}L${at(INNER, angle)}`).join("");
|
|
104
|
+
const FAN_GUARDS = [FIRST, LAST]
|
|
105
|
+
.map((angle) => `M${p(PIVOT)}L${at(OUTER + 2, angle)}`)
|
|
106
|
+
.join("");
|
|
107
|
+
/** The fan (`item(kind="fan")`), open, 100 × 62.79. */
|
|
108
|
+
export const fanArt = {
|
|
109
|
+
Component: () => (_jsxs("g", { ...ITEM_OUTLINE, children: [_jsx("path", { d: FAN_STICKS, strokeWidth: ITEM_DETAIL }), _jsx("path", { d: FAN_LEAF, fill: PALETTE.air }), _jsx("path", { d: FAN_RIBS, strokeWidth: 1.6 }), _jsx("path", { d: FAN_GUARDS, strokeWidth: 5.5 }), _jsx("path", { d: FAN_GUARDS, stroke: PALETTE.bronze, strokeWidth: 2.4 }), _jsx("circle", { cx: PIVOT[0], cy: PIVOT[1], r: 4, fill: PALETTE.bronze, strokeWidth: ITEM_DETAIL })] })),
|
|
110
|
+
box: { width: 100, height: 62.79 },
|
|
111
|
+
variant: sameForEveryNode,
|
|
112
|
+
provenance: fanProvenance,
|
|
113
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HallArt } from "../types.ts";
|
|
2
|
+
export declare const provenance: {
|
|
3
|
+
origin: "original" | "thirdParty";
|
|
4
|
+
author: string;
|
|
5
|
+
license: string;
|
|
6
|
+
source?: string;
|
|
7
|
+
notes?: string;
|
|
8
|
+
};
|
|
9
|
+
/** The pennant's half-width and its top, shoulder and point. */
|
|
10
|
+
export declare const PENNANT: {
|
|
11
|
+
half: number;
|
|
12
|
+
top: number;
|
|
13
|
+
shoulder: number;
|
|
14
|
+
point: number;
|
|
15
|
+
};
|
|
16
|
+
/** The pennant outline: a rectangle ending in a point. */
|
|
17
|
+
export declare function pennantPath(top: number, shoulder: number, point: number): string;
|
|
18
|
+
/** The rod across the top with a knob at each end. */
|
|
19
|
+
export declare function Rod({ y }: {
|
|
20
|
+
y: number;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
22
|
+
/** The Banners of the East and of the West, 100 × 125 units. */
|
|
23
|
+
export declare const bannerArt: HallArt;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { enumAttr } from "../attributes.js";
|
|
3
|
+
import { points } from "./geometry.js";
|
|
4
|
+
import { original, PALETTE, variantOf } from "./shared.js";
|
|
5
|
+
export const provenance = {
|
|
6
|
+
...original("Gadi Cohen's own banner design, drawn as primitive geometry: a " +
|
|
7
|
+
"pennant on a rod with knobs, cord and tassels; the East bears the " +
|
|
8
|
+
"yellow cross with the interlaced red and blue triangles and the white " +
|
|
9
|
+
"Tau, the West the white triangle with the red cross."),
|
|
10
|
+
};
|
|
11
|
+
/** The pennant's half-width and its top, shoulder and point. */
|
|
12
|
+
export const PENNANT = { half: 38, top: -37, shoulder: 27, point: 49 };
|
|
13
|
+
/** The pennant outline: a rectangle ending in a point. */
|
|
14
|
+
export function pennantPath(top, shoulder, point) {
|
|
15
|
+
const { half } = PENNANT;
|
|
16
|
+
return `M${-half},${top}H${half}V${shoulder}L0,${point}L${-half},${shoulder}Z`;
|
|
17
|
+
}
|
|
18
|
+
/** The rod across the top with a knob at each end. */
|
|
19
|
+
export function Rod({ y }) {
|
|
20
|
+
return (_jsxs(_Fragment, { children: [_jsx("rect", { x: -43, y: y - 2, width: 86, height: 4, fill: PALETTE.knob, stroke: PALETTE.ink, strokeWidth: 0.6 }), [-44, 44].map((x) => (_jsx("circle", { cx: x, cy: y, r: 4.5, fill: PALETTE.knob, stroke: PALETTE.ink, strokeWidth: 0.6 }, x)))] }));
|
|
21
|
+
}
|
|
22
|
+
function EastEmblem() {
|
|
23
|
+
return (_jsxs(_Fragment, { children: [_jsx("path", { d: "M-7.5,-24H7.5V-9H23V5.5H7.5V35H-7.5V5.5H-23V-9H-7.5Z", fill: PALETTE.gold }), _jsx("polygon", { points: points([
|
|
24
|
+
[-23, -9],
|
|
25
|
+
[23, -9],
|
|
26
|
+
[0, 28],
|
|
27
|
+
]), fill: "none", stroke: PALETTE.water, strokeWidth: 3.5 }), _jsx("polygon", { points: points([
|
|
28
|
+
[0, -21],
|
|
29
|
+
[23, 16],
|
|
30
|
+
[-23, 16],
|
|
31
|
+
]), fill: "none", stroke: PALETTE.fire, strokeWidth: 3.5 }), _jsx("path", { d: "M-7,-3H7V1.5H2.5V10H-2.5V1.5H-7Z", fill: PALETTE.tau })] }));
|
|
32
|
+
}
|
|
33
|
+
function WestEmblem() {
|
|
34
|
+
return (_jsxs(_Fragment, { children: [_jsx("polygon", { points: points([
|
|
35
|
+
[0, -23],
|
|
36
|
+
[25.6, 20.7],
|
|
37
|
+
[-25.6, 20.7],
|
|
38
|
+
]), fill: "none", stroke: PALETTE.tau, strokeWidth: 4.3 }), _jsx("path", { d: "M-2.3,-2.4H2.3V2H7V7H2.3V15.5H-2.3V7H-7V2H-2.3Z", fill: PALETTE.glow, stroke: PALETTE.knob, strokeWidth: 0.6 })] }));
|
|
39
|
+
}
|
|
40
|
+
const isEast = (node) => enumAttr(node, "kind", ["east", "west"]) !== "west";
|
|
41
|
+
function Banner({ node, defId }) {
|
|
42
|
+
const east = isEast(node);
|
|
43
|
+
const { half, top, shoulder, point } = PENNANT;
|
|
44
|
+
const fill = defId("fill");
|
|
45
|
+
const tassels = [
|
|
46
|
+
[-half, top, top + 15],
|
|
47
|
+
[half, top, top + 15],
|
|
48
|
+
[-half, shoulder, shoulder + 13],
|
|
49
|
+
[half, shoulder, shoulder + 13],
|
|
50
|
+
[0, point, point + 13],
|
|
51
|
+
];
|
|
52
|
+
return (_jsxs(_Fragment, { children: [east && (_jsx("defs", { children: _jsxs("linearGradient", { id: fill, x1: -half, x2: half, y1: 0, y2: 0, gradientUnits: "userSpaceOnUse", children: [_jsx("stop", { offset: 0, stopColor: PALETTE.bannerLightFrom }), _jsx("stop", { offset: 1, stopColor: PALETTE.bannerLightTo })] }) })), _jsx("polyline", { points: points([
|
|
53
|
+
[-half, top],
|
|
54
|
+
[0, -61],
|
|
55
|
+
[half, top],
|
|
56
|
+
]), fill: "none", stroke: east ? PALETTE.tassel : PALETTE.bannerDark, strokeWidth: 2.5, strokeLinejoin: "round" }), _jsx(Rod, { y: top }), _jsx("path", { d: pennantPath(top, shoulder, point), fill: east ? `url(#${fill})` : PALETTE.bannerDark, stroke: PALETTE.ink, strokeWidth: 1.5 }), tassels.map(([x, from, to]) => (_jsx("line", { x1: x, x2: x, y1: from, y2: to, stroke: PALETTE.tassel, strokeWidth: 3.4, strokeLinecap: "round" }, `${x},${from}`))), east ? _jsx(EastEmblem, {}) : _jsx(WestEmblem, {})] }));
|
|
57
|
+
}
|
|
58
|
+
/** The Banners of the East and of the West, 100 × 125 units. */
|
|
59
|
+
export const bannerArt = {
|
|
60
|
+
Component: Banner,
|
|
61
|
+
box: { width: 100, height: 125 },
|
|
62
|
+
variant: (node) => variantOf(isEast(node)),
|
|
63
|
+
provenance,
|
|
64
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HallArt } from "../types.ts";
|
|
2
|
+
export declare const provenance: import("../types.ts").HallArtProvenance;
|
|
3
|
+
/**
|
|
4
|
+
* A board on a stand (`board(kind="flamingSword")`), a host for markers.
|
|
5
|
+
* A plain board is `w` × `h` squares (2 × 3 unless they say otherwise); a
|
|
6
|
+
* board with a diagram is 2 × 3 at `scale=1`, sized by `scale`, so each
|
|
7
|
+
* diagram is drawn once per hall however many boards show it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const boardArt: HallArt;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { TreeFigure } from "../../../kabbalah/treeOfLifeFigure.js";
|
|
3
|
+
import { svgCoordinate } from "../../../svgCoordinate.js";
|
|
4
|
+
import { enumAttr } from "../attributes.js";
|
|
5
|
+
import { GD_HALL_FONT_STACKS } from "../fonts.js";
|
|
6
|
+
import { LINE_HEIGHT, wrapText } from "../labels.js";
|
|
7
|
+
import { BOARD_KINDS, BOARD_NAMES } from "../vocabulary.js";
|
|
8
|
+
import { Platform, sized } from "./platform.js";
|
|
9
|
+
import { original, PALETTE, variantOf } from "./shared.js";
|
|
10
|
+
export const provenance = original("A board on a stand: a white rectangle sized in squares. The Flaming " +
|
|
11
|
+
"Sword and the Minutum Mundum draw the Tree of Life's figure (the " +
|
|
12
|
+
"package's own TreeOfLife geometry, Gadi Cohen); the Garden of Eden " +
|
|
13
|
+
"and the Fall are labelled with their names until they are drawn.");
|
|
14
|
+
/** A board with a diagram: 2 × 3 squares, the Tree's proportions. */
|
|
15
|
+
const DIAGRAM = { width: 2, height: 3 };
|
|
16
|
+
/** The margin inside the board, in squares. */
|
|
17
|
+
const MARGIN = 0.12;
|
|
18
|
+
/** The title's size and the units it is set in (squares × 100). */
|
|
19
|
+
const TITLE_SIZE = 30;
|
|
20
|
+
const kindOf = (node) => enumAttr(node, "kind", BOARD_KINDS);
|
|
21
|
+
const plainBox = sized(2, 3);
|
|
22
|
+
function Board({ node, box }) {
|
|
23
|
+
const kind = kindOf(node);
|
|
24
|
+
const inner = {
|
|
25
|
+
width: box.width - 2 * MARGIN,
|
|
26
|
+
height: box.height - 2 * MARGIN,
|
|
27
|
+
};
|
|
28
|
+
if (kind === "flamingSword" || kind === "minutumMundum")
|
|
29
|
+
return (_jsxs(_Fragment, { children: [_jsx(Platform, { box: box }), _jsx("g", { transform: `translate(${-inner.width / 2} ${-inner.height / 2})`, children: _jsx(TreeFigure, { width: inner.width, height: inner.height, flash: kind === "flamingSword" }) })] }));
|
|
30
|
+
const title = kind ? BOARD_NAMES.get(kind) : undefined;
|
|
31
|
+
// Text at a large size inside a scaled group (decision 6).
|
|
32
|
+
const lines = title ? wrapText(title, inner.width * 100, TITLE_SIZE) : [];
|
|
33
|
+
const advance = TITLE_SIZE * LINE_HEIGHT;
|
|
34
|
+
const first = TITLE_SIZE * 0.36 - ((lines.length - 1) * advance) / 2;
|
|
35
|
+
return (_jsxs(_Fragment, { children: [_jsx(Platform, { box: box }), lines.length > 0 && (_jsx("g", { transform: "scale(0.01)", fill: PALETTE.ink, fontFamily: GD_HALL_FONT_STACKS.serif, fontStyle: "italic", fontSize: TITLE_SIZE, textAnchor: "middle", style: { fontVariantLigatures: "none", fontKerning: "normal" }, children: lines.map((line, index) => (_jsx("text", { y: svgCoordinate(first + index * advance), children: line.text }, index))) }))] }));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A board on a stand (`board(kind="flamingSword")`), a host for markers.
|
|
39
|
+
* A plain board is `w` × `h` squares (2 × 3 unless they say otherwise); a
|
|
40
|
+
* board with a diagram is 2 × 3 at `scale=1`, sized by `scale`, so each
|
|
41
|
+
* diagram is drawn once per hall however many boards show it.
|
|
42
|
+
*/
|
|
43
|
+
export const boardArt = {
|
|
44
|
+
Component: Board,
|
|
45
|
+
box: (node) => (kindOf(node) ? DIAGRAM : plainBox(node)),
|
|
46
|
+
fit: "squares",
|
|
47
|
+
variant: (node) => variantOf(kindOf(node)),
|
|
48
|
+
provenance,
|
|
49
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HallArt, HallAttrSpec } from "../types.ts";
|
|
2
|
+
export declare const provenance: import("../types.ts").HallArtProvenance;
|
|
3
|
+
/** `kind`: which card. */
|
|
4
|
+
export declare const CARD_KIND_SPEC: HallAttrSpec;
|
|
5
|
+
/**
|
|
6
|
+
* An emblem card (`card(kind="pan")`), 58 × 100 like a tarot key: Pan,
|
|
7
|
+
* the Devil, Death or Typhon (Gadi's option (d), 24 October 2026). A
|
|
8
|
+
* card without a kind is blank. Its title cannot be read at a hall's
|
|
9
|
+
* size, so a hall labels it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const cardArt: HallArt;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { enumAttr } from "../attributes.js";
|
|
3
|
+
import { CARD_KINDS, CARD_TITLES } from "../vocabulary.js";
|
|
4
|
+
import { flipped, points, polar, smoothPath } from "./geometry.js";
|
|
5
|
+
import { ITEM_DETAIL, ITEM_OUTLINE, original, PALETTE, variantOf, } from "./shared.js";
|
|
6
|
+
import { CARD_BOX, CardFrame, CardNumeral, CardTitle, romanNumeral, } from "./tarotKey.js";
|
|
7
|
+
export const provenance = original("Emblem cards drawn from geometry in the flat-emblem style, in the " +
|
|
8
|
+
"tarot key's frame: Pan's pipes and crook, the Devil on his cube, " +
|
|
9
|
+
"Death's skull and scythe, Typhon rising from the waves into the " +
|
|
10
|
+
"storm. After the Portal rubric's descriptions (Regardie, The Golden " +
|
|
11
|
+
"Dawn, vol. 2, 1938, pp. 178–184); nothing traced.");
|
|
12
|
+
/** `kind`: which card. */
|
|
13
|
+
export const CARD_KIND_SPEC = {
|
|
14
|
+
kind: "enum",
|
|
15
|
+
values: CARD_KINDS,
|
|
16
|
+
};
|
|
17
|
+
const kindOf = (node) => enumAttr(node, "kind", CARD_KINDS);
|
|
18
|
+
/** The keys among the cards, by their trumps' numbers. */
|
|
19
|
+
const KEYS = new Map([
|
|
20
|
+
["death", 13],
|
|
21
|
+
["devil", 15],
|
|
22
|
+
]);
|
|
23
|
+
/** A stroke drawn as a band with the items' outline: black under, colour over. */
|
|
24
|
+
function Band({ d, width, stroke, }) {
|
|
25
|
+
return (_jsxs("g", { fill: "none", children: [_jsx("path", { d: d, stroke: PALETTE.ink, strokeWidth: width + 7.2 }), _jsx("path", { d: d, stroke: stroke, strokeWidth: width })] }));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pan: the rubric's seven-reeded pipe, its reeds level at the top and
|
|
29
|
+
* each shorter than the last, bound with a red band, and the pastoral
|
|
30
|
+
* staff, a crook, beside it.
|
|
31
|
+
*/
|
|
32
|
+
const tenth = (value) => Math.round(value * 10) / 10 + 0;
|
|
33
|
+
const REEDS = Array.from({ length: 7 }, (_, i) => tenth(-42 + i * 8.6));
|
|
34
|
+
const REED_PATH = REEDS.map((x, i) => {
|
|
35
|
+
const bottom = tenth(29.7 - i * 6.4);
|
|
36
|
+
return `M${tenth(x - 4.3)},-32V${bottom}A4.3,4.3 0 0 0 ${tenth(x + 4.3)},${bottom}V-32Z`;
|
|
37
|
+
}).join("");
|
|
38
|
+
function Pan() {
|
|
39
|
+
return (_jsxs("g", { ...ITEM_OUTLINE, children: [_jsx(Band, { d: "M30,46L30,-30C30,-44 12,-44 12,-33C12,-29 14,-27 17,-27", width: 5, stroke: PALETTE.bronze }), _jsx("path", { d: REED_PATH, fill: PALETTE.bread }), _jsx("g", { fill: PALETTE.ink, stroke: "none", children: REEDS.map((x) => (_jsx("ellipse", { cx: x, cy: -32, rx: 2.6, ry: 1.4 }, x))) }), _jsx("path", { d: "M-46.3,-16H13.9V-8L-46.3,-6Z", fill: PALETTE.fire })] }));
|
|
40
|
+
}
|
|
41
|
+
/** A line of waves at height `y`, across the box. */
|
|
42
|
+
const waves = (y, a) => {
|
|
43
|
+
let d = `M-48,${y}`;
|
|
44
|
+
for (let x = -48; x < 48; x += 16)
|
|
45
|
+
d += `Q${x + 4},${y - a} ${x + 8},${y}T${x + 16},${y}`;
|
|
46
|
+
return `${d}V48H-48Z`;
|
|
47
|
+
};
|
|
48
|
+
const TYPHON_BODY = smoothPath([
|
|
49
|
+
[-30, 34],
|
|
50
|
+
[-14, 30],
|
|
51
|
+
[6, 24],
|
|
52
|
+
[16, 14],
|
|
53
|
+
[8, 4],
|
|
54
|
+
[-12, -2],
|
|
55
|
+
[-20, -12],
|
|
56
|
+
[-10, -22],
|
|
57
|
+
[6, -28],
|
|
58
|
+
], 1);
|
|
59
|
+
/** The storm cloud's billows: x, y and radius. */
|
|
60
|
+
const CLOUD = [
|
|
61
|
+
[-22, -34, 13],
|
|
62
|
+
[-4, -40, 15],
|
|
63
|
+
[16, -36, 13],
|
|
64
|
+
[30, -30, 10],
|
|
65
|
+
[-34, -28, 9],
|
|
66
|
+
[6, -27, 12],
|
|
67
|
+
[-16, -26, 10],
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* Typhon, "the Destroyer": a red serpent rising in coils from the waves
|
|
71
|
+
* (Earth and Ocean) into a storm cloud, his head lost in it, a yellow
|
|
72
|
+
* bolt below.
|
|
73
|
+
*/
|
|
74
|
+
function Typhon() {
|
|
75
|
+
return (_jsxs("g", { ...ITEM_OUTLINE, children: [_jsx("path", { d: waves(32, 5), fill: PALETTE.water }), _jsx(Band, { d: TYPHON_BODY, width: 11, stroke: PALETTE.fire }), _jsx("path", { d: waves(40, 4), fill: PALETTE.water }), _jsx("g", { fill: PALETTE.storm, children: CLOUD.map(([x, y, r]) => (_jsx("circle", { cx: x, cy: y, r: r }, `${x},${y}`))) }), _jsx("g", { fill: PALETTE.storm, stroke: "none", children: CLOUD.map(([x, y, r]) => (_jsx("circle", { cx: x, cy: y, r: r - 1.8 }, `${x},${y}`))) }), _jsx("path", { d: "M2,-24L-6,-10L2,-12L-4,2", fill: "none", stroke: PALETTE.air, strokeWidth: 3.4 })] }));
|
|
76
|
+
}
|
|
77
|
+
/** Death: a skull, and the scythe he reaps with. */
|
|
78
|
+
function Death() {
|
|
79
|
+
return (_jsxs("g", { ...ITEM_OUTLINE, children: [_jsx(Band, { d: "M26,46L12,-38", width: 5, stroke: PALETTE.bronze }), _jsx("path", { d: "M13,-40C-4,-48 -30,-44 -46,-24C-28,-36 -6,-36 15,-30Z", fill: PALETTE.silver }), _jsx("path", { d: "M-30,6A20,20 0 1 1 6,6C6,12 2,14 0,16V24H-24V16C-26,14 -30,12 -30,6Z", fill: PALETTE.paper }), _jsxs("g", { fill: PALETTE.ink, stroke: "none", children: [_jsx("ellipse", { cx: -20, cy: 4, rx: 5.6, ry: 6.4 }), _jsx("ellipse", { cx: -4, cy: 4, rx: 5.6, ry: 6.4 }), _jsx("path", { d: "M-12,10L-15,17H-9Z" })] }), _jsx("path", { d: "M-18,24V19M-12,24V19M-6,24V19", fill: "none", strokeWidth: ITEM_DETAIL })] }));
|
|
80
|
+
}
|
|
81
|
+
/** A bat's wing, the right one: finger points along the top, scalloped. */
|
|
82
|
+
const BAT_WING = "M8,-6C18,-20 34,-26 48,-22L44,-10C40,0 42,8 46,16C38,10 32,10 28,16" +
|
|
83
|
+
"C24,8 18,8 14,14C14,6 12,0 8,-2Z";
|
|
84
|
+
const BAT_WINGS = BAT_WING + flipped(BAT_WING);
|
|
85
|
+
/** The reversed pentagram over his head: a point down. */
|
|
86
|
+
const REVERSED_PENTAGRAM = `M${points(Array.from({ length: 5 }, (_, i) => polar(8, 90 + i * 144, [0, -36]))).replaceAll(" ", "L")}Z`;
|
|
87
|
+
/**
|
|
88
|
+
* The Devil: bat wings, a red horned head under a reversed pentagram,
|
|
89
|
+
* standing on the cubical altar (the key as the rubric describes it).
|
|
90
|
+
*/
|
|
91
|
+
function Devil() {
|
|
92
|
+
return (_jsxs("g", { ...ITEM_OUTLINE, children: [_jsx("path", { d: BAT_WINGS, fill: PALETTE.bannerDark }), _jsx("path", { d: "M-18,22H18V44H-18Z", fill: PALETTE.bannerDark }), _jsx("path", { d: "M-18,22L-12,16H24L18,22M24,16V38L18,44", fill: PALETTE.storm }), _jsx("path", { d: "M-7,-14C-18,-17 -23,-26 -20,-35C-17,-27 -11,-23 -3,-19Z", fill: PALETTE.goldDeep }), _jsx("path", { d: "M7,-14C18,-17 23,-26 20,-35C17,-27 11,-23 3,-19Z", fill: PALETTE.goldDeep }), _jsx("path", { d: "M-12,-16C-6,-20 6,-20 12,-16C13,-6 8,4 4,10L0,18L-4,10C-8,4 -13,-6 -12,-16Z", fill: PALETTE.fire }), _jsx("path", { d: "M-9,-8L-3,-6M9,-8L3,-6", stroke: PALETTE.paper, strokeWidth: 3.2 }), _jsx("path", { d: REVERSED_PENTAGRAM, fill: "none", stroke: PALETTE.ink, strokeWidth: 5.6 }), _jsx("path", { d: REVERSED_PENTAGRAM, fill: "none", stroke: PALETTE.paper, strokeWidth: 2.2 })] }));
|
|
93
|
+
}
|
|
94
|
+
const EMBLEMS = new Map([
|
|
95
|
+
["pan", Pan],
|
|
96
|
+
["devil", Devil],
|
|
97
|
+
["death", Death],
|
|
98
|
+
["typhon", Typhon],
|
|
99
|
+
]);
|
|
100
|
+
/**
|
|
101
|
+
* An emblem card: the tarot key's frame with the emblem in it, the
|
|
102
|
+
* title below and, on a key, its numeral above. The emblem is drawn in a
|
|
103
|
+
* 100-unit box at 0.44, so its outline matches the frame's.
|
|
104
|
+
*/
|
|
105
|
+
function Card({ node }) {
|
|
106
|
+
const kind = kindOf(node);
|
|
107
|
+
const Emblem = kind && EMBLEMS.get(kind);
|
|
108
|
+
const key = kind && KEYS.get(kind);
|
|
109
|
+
return (_jsxs(_Fragment, { children: [_jsx(CardFrame, {}), kind && Emblem && (_jsxs(_Fragment, { children: [_jsx("g", { transform: `translate(0 ${key ? 2 : -3}) scale(0.44)`, children: _jsx(Emblem, {}) }), _jsxs("g", { fill: PALETTE.ink, textAnchor: "middle", children: [key && _jsx(CardNumeral, { numeral: romanNumeral(key) }), _jsx(CardTitle, { title: CARD_TITLES.get(kind) })] })] }))] }));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* An emblem card (`card(kind="pan")`), 58 × 100 like a tarot key: Pan,
|
|
113
|
+
* the Devil, Death or Typhon (Gadi's option (d), 24 October 2026). A
|
|
114
|
+
* card without a kind is blank. Its title cannot be read at a hall's
|
|
115
|
+
* size, so a hall labels it.
|
|
116
|
+
*/
|
|
117
|
+
export const cardArt = {
|
|
118
|
+
Component: Card,
|
|
119
|
+
box: CARD_BOX,
|
|
120
|
+
attributes: new Map([["kind", CARD_KIND_SPEC]]),
|
|
121
|
+
// One of four cards, or a blank one.
|
|
122
|
+
variant: (node) => variantOf(kindOf(node)),
|
|
123
|
+
provenance,
|
|
124
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HallArt } from "../types.ts";
|
|
2
|
+
export declare const gongProvenance: import("../types.ts").HallArtProvenance;
|
|
3
|
+
export declare const brazierProvenance: import("../types.ts").HallArtProvenance;
|
|
4
|
+
export declare const candleProvenance: import("../types.ts").HallArtProvenance;
|
|
5
|
+
/** Every art module exports its provenance; these three share a module. */
|
|
6
|
+
export declare const provenance: import("../types.ts").HallArtProvenance[];
|
|
7
|
+
/** A gong (`item(kind="gong")`), the Portal's, 100 × 100. */
|
|
8
|
+
export declare const gongArt: HallArt;
|
|
9
|
+
/** A brazier (`item(kind="brazier")`): a bowl of fire on legs, 100 × 100. */
|
|
10
|
+
export declare const brazierArt: HallArt;
|
|
11
|
+
/** A candle (`item(kind="candle")`) on its stick, 40 × 100. */
|
|
12
|
+
export declare const candleArt: HallArt;
|