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.
Files changed (173) hide show
  1. package/LICENSE.txt +89 -0
  2. package/README.md +353 -2
  3. package/fonts/FrankRuhlLibre-Bold-letters.woff2 +0 -0
  4. package/fonts/FrankRuhlLibre-OFL.txt +105 -0
  5. package/fonts/NotoSerif-BoldItalic-compass.woff2 +0 -0
  6. package/fonts/NotoSerif-Italic-latin1.woff2 +0 -0
  7. package/fonts/NotoSerif-OFL.txt +93 -0
  8. package/fonts/SOURCES.md +35 -0
  9. package/fonts/gd-hall.css +3 -0
  10. package/lib/astrology/Kamea.d.ts +75 -0
  11. package/lib/astrology/Kamea.js +73 -0
  12. package/lib/astrology/MercuryWidget.d.ts +21 -0
  13. package/lib/astrology/MercuryWidget.js +80 -0
  14. package/lib/astrology/Moon.d.ts +32 -0
  15. package/lib/astrology/Moon.js +39 -0
  16. package/lib/astrology/MoonWidget.d.ts +20 -0
  17. package/lib/astrology/MoonWidget.js +63 -0
  18. package/lib/astrology/PlanetarySpirit.d.ts +103 -0
  19. package/lib/astrology/PlanetarySpirit.js +102 -0
  20. package/lib/astrology/mercuryRetrograde.d.ts +20 -0
  21. package/lib/astrology/mercuryRetrograde.js +80 -0
  22. package/lib/astrology/moonPath.d.ts +20 -0
  23. package/lib/astrology/moonPath.js +45 -0
  24. package/lib/chakras/Chakras.d.ts +34 -0
  25. package/lib/chakras/Chakras.js +149 -0
  26. package/lib/cssString.d.ts +5 -0
  27. package/lib/cssString.js +7 -0
  28. package/lib/drawingVersions.d.ts +29 -0
  29. package/lib/drawingVersions.js +15 -0
  30. package/lib/enochian/Tablet.d.ts +17 -0
  31. package/lib/enochian/Tablet.js +24 -0
  32. package/lib/enochian/tabletSigils.d.ts +46 -0
  33. package/lib/enochian/tabletSigils.js +172 -0
  34. package/lib/enochian/tabletSvg.d.ts +85 -0
  35. package/lib/enochian/tabletSvg.js +278 -0
  36. package/lib/gd/FylfotCross.d.ts +60 -0
  37. package/lib/gd/FylfotCross.js +121 -0
  38. package/lib/gd/GradeTree.d.ts +10 -0
  39. package/lib/gd/GradeTree.js +21 -0
  40. package/lib/gd/Lamen.d.ts +31 -0
  41. package/lib/gd/Lamen.js +317 -0
  42. package/lib/gd/RoseSigil.d.ts +31 -0
  43. package/lib/gd/RoseSigil.js +49 -0
  44. package/lib/gd/RoseSigilImage.d.ts +38 -0
  45. package/lib/gd/RoseSigilImage.js +29 -0
  46. package/lib/gd/SevenBranchedCandlestick.d.ts +54 -0
  47. package/lib/gd/SevenBranchedCandlestick.js +269 -0
  48. package/lib/gd/TableOfShewbread.d.ts +19 -0
  49. package/lib/gd/TableOfShewbread.js +120 -0
  50. package/lib/gd/hall/GdHall.d.ts +94 -0
  51. package/lib/gd/hall/GdHall.js +147 -0
  52. package/lib/gd/hall/art/altar.d.ts +8 -0
  53. package/lib/gd/hall/art/altar.js +21 -0
  54. package/lib/gd/hall/art/altarItems.d.ts +20 -0
  55. package/lib/gd/hall/art/altarItems.js +113 -0
  56. package/lib/gd/hall/art/banner.d.ts +23 -0
  57. package/lib/gd/hall/art/banner.js +64 -0
  58. package/lib/gd/hall/art/board.d.ts +9 -0
  59. package/lib/gd/hall/art/board.js +49 -0
  60. package/lib/gd/hall/art/cards.d.ts +11 -0
  61. package/lib/gd/hall/art/cards.js +124 -0
  62. package/lib/gd/hall/art/furnishings.d.ts +12 -0
  63. package/lib/gd/hall/art/furnishings.js +36 -0
  64. package/lib/gd/hall/art/geometry.d.ts +49 -0
  65. package/lib/gd/hall/art/geometry.js +97 -0
  66. package/lib/gd/hall/art/hebrewLetter.d.ts +10 -0
  67. package/lib/gd/hall/art/hebrewLetter.js +38 -0
  68. package/lib/gd/hall/art/index.d.ts +7 -0
  69. package/lib/gd/hall/art/index.js +61 -0
  70. package/lib/gd/hall/art/items.d.ts +12 -0
  71. package/lib/gd/hall/art/items.js +125 -0
  72. package/lib/gd/hall/art/kamea.d.ts +20 -0
  73. package/lib/gd/hall/art/kamea.js +64 -0
  74. package/lib/gd/hall/art/lamen.d.ts +9 -0
  75. package/lib/gd/hall/art/lamen.js +52 -0
  76. package/lib/gd/hall/art/lightning.d.ts +13 -0
  77. package/lib/gd/hall/art/lightning.js +44 -0
  78. package/lib/gd/hall/art/lights.d.ts +13 -0
  79. package/lib/gd/hall/art/lights.js +64 -0
  80. package/lib/gd/hall/art/marker.d.ts +8 -0
  81. package/lib/gd/hall/art/marker.js +19 -0
  82. package/lib/gd/hall/art/pantacle.d.ts +10 -0
  83. package/lib/gd/hall/art/pantacle.js +119 -0
  84. package/lib/gd/hall/art/pillar.d.ts +4 -0
  85. package/lib/gd/hall/art/pillar.js +25 -0
  86. package/lib/gd/hall/art/placeholder.d.ts +8 -0
  87. package/lib/gd/hall/art/placeholder.js +18 -0
  88. package/lib/gd/hall/art/platform.d.ts +13 -0
  89. package/lib/gd/hall/art/platform.js +28 -0
  90. package/lib/gd/hall/art/seals.d.ts +23 -0
  91. package/lib/gd/hall/art/seals.js +151 -0
  92. package/lib/gd/hall/art/shared.d.ts +87 -0
  93. package/lib/gd/hall/art/shared.js +95 -0
  94. package/lib/gd/hall/art/symbols.d.ts +37 -0
  95. package/lib/gd/hall/art/symbols.js +134 -0
  96. package/lib/gd/hall/art/tarotKey.d.ts +34 -0
  97. package/lib/gd/hall/art/tarotKey.js +101 -0
  98. package/lib/gd/hall/art/veil.d.ts +8 -0
  99. package/lib/gd/hall/art/veil.js +22 -0
  100. package/lib/gd/hall/art/vessels.d.ts +19 -0
  101. package/lib/gd/hall/art/vessels.js +74 -0
  102. package/lib/gd/hall/aspect.d.ts +31 -0
  103. package/lib/gd/hall/aspect.js +161 -0
  104. package/lib/gd/hall/attributes.d.ts +52 -0
  105. package/lib/gd/hall/attributes.js +116 -0
  106. package/lib/gd/hall/describe.d.ts +38 -0
  107. package/lib/gd/hall/describe.js +184 -0
  108. package/lib/gd/hall/dimensions.d.ts +39 -0
  109. package/lib/gd/hall/dimensions.js +49 -0
  110. package/lib/gd/hall/editor.d.ts +13 -0
  111. package/lib/gd/hall/editor.js +13 -0
  112. package/lib/gd/hall/fontMetrics.json +177 -0
  113. package/lib/gd/hall/fonts.d.ts +45 -0
  114. package/lib/gd/hall/fonts.js +73 -0
  115. package/lib/gd/hall/hebrewLetters.d.ts +8 -0
  116. package/lib/gd/hall/hebrewLetters.js +40 -0
  117. package/lib/gd/hall/index.d.ts +19 -0
  118. package/lib/gd/hall/index.js +17 -0
  119. package/lib/gd/hall/labels.d.ts +72 -0
  120. package/lib/gd/hall/labels.js +109 -0
  121. package/lib/gd/hall/layout.d.ts +213 -0
  122. package/lib/gd/hall/layout.js +837 -0
  123. package/lib/gd/hall/planets.d.ts +13 -0
  124. package/lib/gd/hall/planets.js +35 -0
  125. package/lib/gd/hall/presets/index.d.ts +13 -0
  126. package/lib/gd/hall/presets/index.js +29 -0
  127. package/lib/gd/hall/presets/neophyte.d.ts +7 -0
  128. package/lib/gd/hall/presets/neophyte.js +74 -0
  129. package/lib/gd/hall/presets/philosophus.d.ts +30 -0
  130. package/lib/gd/hall/presets/philosophus.js +244 -0
  131. package/lib/gd/hall/presets/portal.d.ts +24 -0
  132. package/lib/gd/hall/presets/portal.js +241 -0
  133. package/lib/gd/hall/presets/practicus.d.ts +17 -0
  134. package/lib/gd/hall/presets/practicus.js +181 -0
  135. package/lib/gd/hall/presets/theoricus.d.ts +13 -0
  136. package/lib/gd/hall/presets/theoricus.js +178 -0
  137. package/lib/gd/hall/presets/types.d.ts +72 -0
  138. package/lib/gd/hall/presets/types.js +58 -0
  139. package/lib/gd/hall/presets/zelator.d.ts +12 -0
  140. package/lib/gd/hall/presets/zelator.js +161 -0
  141. package/lib/gd/hall/spec.d.ts +54 -0
  142. package/lib/gd/hall/spec.js +108 -0
  143. package/lib/gd/hall/types.d.ts +131 -0
  144. package/lib/gd/hall/types.js +1 -0
  145. package/lib/gd/hall/validate.d.ts +13 -0
  146. package/lib/gd/hall/validate.js +285 -0
  147. package/lib/gd/hall/vocabulary.d.ts +139 -0
  148. package/lib/gd/hall/vocabulary.js +435 -0
  149. package/lib/gd/lamenGlyphs.d.ts +16 -0
  150. package/lib/gd/lamenGlyphs.js +167 -0
  151. package/lib/gd/lamenGrades.d.ts +29 -0
  152. package/lib/gd/lamenGrades.js +36 -0
  153. package/lib/gd/roseSigilGeometry.d.ts +10 -0
  154. package/lib/gd/roseSigilGeometry.js +10 -0
  155. package/lib/gd/roseSigilInternals.d.ts +85 -0
  156. package/lib/gd/roseSigilInternals.js +343 -0
  157. package/lib/gd/roseSigilOptimizer.d.ts +15 -0
  158. package/lib/gd/roseSigilOptimizer.js +59 -0
  159. package/lib/gd/roseSigilVersions.d.ts +12 -0
  160. package/lib/gd/roseSigilVersions.js +57 -0
  161. package/lib/hooks/useHydrated.d.ts +9 -0
  162. package/lib/hooks/useHydrated.js +13 -0
  163. package/lib/kabbalah/TreeOfLife.d.ts +135 -0
  164. package/lib/kabbalah/TreeOfLife.js +312 -0
  165. package/lib/kabbalah/fieldPath.d.ts +25 -0
  166. package/lib/kabbalah/fieldPath.js +100 -0
  167. package/lib/kabbalah/treeLabels.d.ts +30 -0
  168. package/lib/kabbalah/treeLabels.js +32 -0
  169. package/lib/kabbalah/treeOfLifeFigure.d.ts +59 -0
  170. package/lib/kabbalah/treeOfLifeFigure.js +151 -0
  171. package/lib/svgCoordinate.d.ts +12 -0
  172. package/lib/svgCoordinate.js +16 -0
  173. package/package.json +162 -3
@@ -0,0 +1,13 @@
1
+ import type { HallArt, HallNode } from "../types.ts";
2
+ import { type LightColor } from "../vocabulary.ts";
3
+ /**
4
+ * A light's colour: its `light`, else the default of its kind (a lamp
5
+ * red, a lantern yellow). Halls written before stage H named the glass
6
+ * with `fill`, which still counts when it is one of the light colours.
7
+ */
8
+ export declare function lightColor(node: HallNode, fallback: LightColor): "red" | "white" | "yellow";
9
+ export declare const provenance: import("../types.ts").HallArtProvenance;
10
+ /** A lamp (`item(kind="lamp")`), red unless its `light` says otherwise. */
11
+ export declare const lampArt: HallArt;
12
+ /** A lantern (`item(kind="lantern")`), yellow unless `light` says otherwise. */
13
+ export declare const lanternArt: HallArt;
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { enumAttr } from "../attributes.js";
3
+ import { LIGHT_COLORS } from "../vocabulary.js";
4
+ import { pathPoint } from "./geometry.js";
5
+ import { HIGHLIGHT, ITEM_DETAIL, ITEM_OUTLINE, original, PALETTE, } from "./shared.js";
6
+ const GLASS = new Map([
7
+ ["red", PALETTE.glassRed],
8
+ ["yellow", PALETTE.glassYellow],
9
+ ["white", PALETTE.glassWhite],
10
+ ]);
11
+ /**
12
+ * A light's colour: its `light`, else the default of its kind (a lamp
13
+ * red, a lantern yellow). Halls written before stage H named the glass
14
+ * with `fill`, which still counts when it is one of the light colours.
15
+ */
16
+ export function lightColor(node, fallback) {
17
+ return (enumAttr(node, "light", LIGHT_COLORS) ??
18
+ enumAttr(node, "fill", LIGHT_COLORS) ??
19
+ fallback);
20
+ }
21
+ /** The attribute the lamp and the lantern add to an item's. */
22
+ const LIGHT_ATTRIBUTES = new Map([
23
+ ["light", { kind: "enum", values: LIGHT_COLORS }],
24
+ ]);
25
+ export const provenance = original("A lantern drawn from geometry: a ring, a roof, glazed panes in the " +
26
+ "colour of its light, a flame, a stepped base.");
27
+ /** A teardrop flame `h` tall, centred on the axis at `cy`. */
28
+ function flame(cy, h) {
29
+ const w = h * 0.36;
30
+ const top = cy - h / 2;
31
+ const bottom = cy + h / 2;
32
+ const shoulder = top + h * 0.42;
33
+ const p = (x, y) => pathPoint([x, y]);
34
+ return (`M${p(0, top)}C${p(w * 1.1, shoulder)} ${p(w, bottom)} ${p(0, bottom)}` +
35
+ `C${p(-w, bottom)} ${p(-w * 1.1, shoulder)} ${p(0, top)}Z`);
36
+ }
37
+ const FLAME = flame(10, 18);
38
+ const FLAME_CORE = flame(12.88, 9);
39
+ const PANES = "M-11,-18H11V29H-11ZM-19.5,-18H-14.5V29H-19.5ZM14.5,-18H19.5V29H14.5Z";
40
+ /** The lantern, its panes glazed in the colour of its light. */
41
+ function Lantern({ glass }) {
42
+ return (_jsxs("g", { ...ITEM_OUTLINE, children: [_jsx("circle", { cy: -45, r: 3, fill: "none", strokeWidth: ITEM_DETAIL }), _jsx("path", { d: "M-6,-41H6L26,-28H-26Z", fill: PALETTE.bronze }), _jsx("rect", { x: -28, y: -28.5, width: 56, height: 5, rx: 1.5, fill: PALETTE.bronze }), _jsx("rect", { x: -22.5, y: -23.5, width: 45, height: 57, fill: PALETTE.bronze }), _jsx("path", { d: PANES, fill: glass, strokeWidth: ITEM_DETAIL }), _jsx("path", { d: FLAME, fill: PALETTE.flame, stroke: PALETTE.fire, strokeWidth: ITEM_DETAIL }), _jsx("path", { d: FLAME_CORE, fill: PALETTE.air, stroke: "none" }), _jsx("path", { d: "M-22.5,33.5H22.5L26.5,40H-26.5Z", fill: PALETTE.bronze }), _jsx("rect", { x: -27.5, y: 40, width: 55, height: 6.5, rx: 1.5, fill: PALETTE.bronze }), _jsx("path", { d: "M-3.5,-37.5L-16,-30", ...HIGHLIGHT })] }));
43
+ }
44
+ /**
45
+ * The lamp and the lantern: one drawing, as in the old drawings, whose
46
+ * lanterns were the lamp stretched. They stay two kinds because their
47
+ * default scales and colours differ (a lamp is red and 0.94 squares
48
+ * tall, a lantern yellow and 2.38), and saved halls and the presets name
49
+ * both. `light` picks the colour of either.
50
+ */
51
+ const lightArt = (fallback) => {
52
+ const glassOf = (node) => GLASS.get(lightColor(node, fallback));
53
+ return {
54
+ Component: ({ node }) => _jsx(Lantern, { glass: glassOf(node) }),
55
+ box: { width: 60.09, height: 100 },
56
+ attributes: LIGHT_ATTRIBUTES,
57
+ variant: (node) => lightColor(node, fallback),
58
+ provenance,
59
+ };
60
+ };
61
+ /** A lamp (`item(kind="lamp")`), red unless its `light` says otherwise. */
62
+ export const lampArt = lightArt("red");
63
+ /** A lantern (`item(kind="lantern")`), yellow unless `light` says otherwise. */
64
+ export const lanternArt = lightArt("yellow");
@@ -0,0 +1,8 @@
1
+ import type { HallArt } from "../types.ts";
2
+ export declare const provenance: import("../types.ts").HallArtProvenance;
3
+ /**
4
+ * A marker: text in a box or circle, for objects without art yet. The
5
+ * layout sets the text over it (`HallResolvedObject.inner`), so it counts
6
+ * against the hall's label lines.
7
+ */
8
+ export declare const markerArt: HallArt;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { enumAttr } from "../attributes.js";
3
+ import { original, PALETTE, variantOf } from "./shared.js";
4
+ export const provenance = original("A labelled box or circle, as the book marks objects and the candidate.");
5
+ const isCircle = (node) => enumAttr(node, "shape", ["box", "circle"]) === "circle";
6
+ function Marker({ node }) {
7
+ return isCircle(node) ? (_jsx("circle", { r: 48, fill: PALETTE.paper, stroke: PALETTE.ink, strokeWidth: 3 })) : (_jsx("rect", { x: -48, y: -48, width: 96, height: 96, fill: PALETTE.paper, stroke: PALETTE.ink, strokeWidth: 3 }));
8
+ }
9
+ /**
10
+ * A marker: text in a box or circle, for objects without art yet. The
11
+ * layout sets the text over it (`HallResolvedObject.inner`), so it counts
12
+ * against the hall's label lines.
13
+ */
14
+ export const markerArt = {
15
+ Component: Marker,
16
+ box: { width: 100, height: 100 },
17
+ variant: (node) => variantOf(isCircle(node)),
18
+ provenance,
19
+ };
@@ -0,0 +1,10 @@
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 Theoricus' Pantacle (`item(kind="pantacle")`), 100 × 100. */
10
+ export declare const pantacleArt: HallArt;
@@ -0,0 +1,119 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { svgCoordinate } from "../../../svgCoordinate.js";
3
+ import { GD_HALL_FONT_STACKS } from "../fonts.js";
4
+ import { hebrewFont, labelFont } from "../labels.js";
5
+ import { points } from "./geometry.js";
6
+ import { original, PALETTE, sameForEveryNode } from "./shared.js";
7
+ export const provenance = {
8
+ ...original("The Pantacle drawn from geometry after Gadi Cohen's own Theoricus " +
9
+ "badge design: the Earth disc in its four " +
10
+ "colours, an interlaced hexagram and a ring naming the Earth's " +
11
+ "correspondences. The names are set letter by letter in the hall fonts " +
12
+ "(SIL OFL 1.1); no glyph outlines are copied into the code."),
13
+ };
14
+ /** Radii in the 100-unit box. */
15
+ const OUTER = 49;
16
+ const DISC = 40.5;
17
+ /** Where the ring's text stands. */
18
+ const BASELINE = 42.6;
19
+ /** The hexagram's centre line and band. */
20
+ const STAR = 32;
21
+ const BAND = 5;
22
+ const EDGE = 1.4;
23
+ /** The Earth's colours, quartered as in the drawing: citrine above. */
24
+ const QUARTERS = [
25
+ { angle: -90, fill: PALETTE.citrine }, // East (up)
26
+ { angle: 0, fill: PALETTE.olive },
27
+ { angle: 90, fill: PALETTE.earthBlack },
28
+ { angle: 180, fill: PALETTE.russet },
29
+ ];
30
+ /**
31
+ * The ring: "SAPERE AUDE" at the top, then the Earth's names in the
32
+ * order the drawing gives them (its "אאאאדני" read as אדני), each in an
33
+ * eighth of the ring, reading on in the Hebrew direction (anticlockwise).
34
+ */
35
+ const RING = [
36
+ { text: "SAPERE AUDE", hebrew: false },
37
+ ...["אדני", "אוריאל", "כרוב", "פרת", "צפון", "פורלאך", "ארץ"].map((text) => ({
38
+ text,
39
+ hebrew: true,
40
+ })),
41
+ ];
42
+ /** Sizes of the ring's letters, in units. */
43
+ const LATIN_SIZE = 5;
44
+ const HEBREW_SIZE = 6.4;
45
+ /**
46
+ * Where each letter of a name stands, its baseline on the ring and its top
47
+ * outward. Measured in logical order with the font's advances and kern
48
+ * pairs; Latin runs clockwise and Hebrew anticlockwise, so both read on
49
+ * from the name before. Each letter is drawn alone, so no renderer's bidi
50
+ * or text-on-path support (resvg reverses Hebrew on a path; rsvg draws no
51
+ * `textPath`) changes the order.
52
+ */
53
+ function ringGlyphs(text, center, font, size, rightToLeft) {
54
+ const chars = [...text];
55
+ const total = font.width(text, size);
56
+ return chars.flatMap((char, index) => {
57
+ if (char === " ")
58
+ return [];
59
+ // The letter's centre, from the start of the name in reading order.
60
+ const along = font.width(chars.slice(0, index + 1).join(""), size) -
61
+ font.width(char, size) / 2;
62
+ const fromMiddle = along - total / 2;
63
+ const radians = (rightToLeft ? -fromMiddle : fromMiddle) / BASELINE;
64
+ return [{ char, angle: center + (radians * 180) / Math.PI }];
65
+ });
66
+ }
67
+ /** The ring's names, each centred in its eighth, anticlockwise from the top. */
68
+ const RING_GLYPHS = RING.map((name, index) => ({
69
+ ...name,
70
+ glyphs: ringGlyphs(name.text, 270 - (index * 360) / RING.length, name.hebrew ? hebrewFont : labelFont, name.hebrew ? HEBREW_SIZE : LATIN_SIZE, name.hebrew),
71
+ }));
72
+ const at = (angle, r) => {
73
+ const radians = (angle * Math.PI) / 180;
74
+ return [
75
+ svgCoordinate(r * Math.cos(radians)),
76
+ svgCoordinate(r * Math.sin(radians)),
77
+ ];
78
+ };
79
+ const wedge = (angle) => {
80
+ const [x0, y0] = at(angle - 45, DISC);
81
+ const [x1, y1] = at(angle + 45, DISC);
82
+ return `M0,0L${x0},${y0}A${DISC},${DISC} 0 0 1 ${x1},${y1}Z`;
83
+ };
84
+ const UP = [-90, 30, 150].map((angle) => at(angle, STAR));
85
+ const DOWN = [90, 210, 330].map((angle) => at(angle, STAR));
86
+ /**
87
+ * Where the down triangle passes over the up one: three of the six
88
+ * crossings, alternating, as short pieces of its edges redrawn on top. The
89
+ * white piece runs a little past the black one, so no seam shows where the
90
+ * piece ends on the band it continues.
91
+ */
92
+ const OVER = [0, 120, 240].map((angle) => {
93
+ const [x, y] = at(angle, STAR / Math.sqrt(3));
94
+ // The down triangle's edge through this crossing runs at angle + 120°.
95
+ const segment = (half) => {
96
+ const [dx, dy] = at(angle + 120, half);
97
+ return points([
98
+ [x - dx, y - dy],
99
+ [x + dx, y + dy],
100
+ ]);
101
+ };
102
+ // Long enough to cross the other band (half its width over sin 60°, plus
103
+ // its slant), short of the next crossing a third of the edge away.
104
+ const black = 1.1 * (BAND + 2 * EDGE);
105
+ return { black: segment(black), white: segment(black + 1) };
106
+ });
107
+ function Band({ pointsList }) {
108
+ return (_jsxs(_Fragment, { children: [_jsx("polygon", { points: pointsList, fill: "none", stroke: PALETTE.ink, strokeWidth: BAND + 2 * EDGE, strokeLinejoin: "miter" }), _jsx("polygon", { points: pointsList, fill: "none", stroke: PALETTE.paper, strokeWidth: BAND, strokeLinejoin: "miter" })] }));
109
+ }
110
+ function Pantacle() {
111
+ return (_jsxs(_Fragment, { children: [_jsx("circle", { r: OUTER, fill: PALETTE.paper, stroke: PALETTE.ink, strokeWidth: 1.2 }), QUARTERS.map((quarter) => (_jsx("path", { d: wedge(quarter.angle), fill: quarter.fill }, quarter.angle))), _jsx("circle", { r: DISC, fill: "none", stroke: PALETTE.ink, strokeWidth: 0.8 }), _jsx(Band, { pointsList: points(DOWN) }), _jsx(Band, { pointsList: points(UP) }), OVER.map((segment) => (_jsxs("g", { children: [_jsx("polyline", { points: segment.black, stroke: PALETTE.ink, strokeWidth: BAND + 2 * EDGE }), _jsx("polyline", { points: segment.white, stroke: PALETTE.paper, strokeWidth: BAND })] }, segment.black))), RING_GLYPHS.map((name) => (_jsx("g", { fontFamily: name.hebrew ? GD_HALL_FONT_STACKS.hebrew : GD_HALL_FONT_STACKS.serif, fontStyle: name.hebrew ? undefined : "italic", fontWeight: name.hebrew ? 700 : undefined, fontSize: name.hebrew ? HEBREW_SIZE : LATIN_SIZE, fill: PALETTE.ink, textAnchor: "middle", children: name.glyphs.map((glyph) => (_jsx("text", { y: -BASELINE, transform: `rotate(${svgCoordinate((glyph.angle + 450) % 360)})`, children: glyph.char }, glyph.angle))) }, name.text)))] }));
112
+ }
113
+ /** The Theoricus' Pantacle (`item(kind="pantacle")`), 100 × 100. */
114
+ export const pantacleArt = {
115
+ Component: Pantacle,
116
+ box: { width: 100, height: 100 },
117
+ variant: sameForEveryNode,
118
+ provenance,
119
+ };
@@ -0,0 +1,4 @@
1
+ import type { HallArt } from "../types.ts";
2
+ export declare const provenance: import("../types.ts").HallArtProvenance;
3
+ /** A pillar: black (downward triangle) or white (upward), in 2 × 2 squares. */
4
+ export declare const pillarArt: HallArt;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { booleanAttr, enumAttr } from "../attributes.js";
3
+ import { triangle } from "./geometry.js";
4
+ import { original, PALETTE, variantOf } from "./shared.js";
5
+ export const provenance = original("Redrawn from geometry after Gadi Cohen's hall drawings: a disc with a " +
6
+ "downward (black) or upward (white) triangle; glow adds the Neophyte " +
7
+ "drawing's red radial light inside a grey-rimmed triangle.");
8
+ const isBlack = (node) => enumAttr(node, "kind", ["black", "white"]) !== "white";
9
+ const glows = (node) => booleanAttr(node, "glow") === true;
10
+ function Pillar({ node, defId }) {
11
+ const black = isBlack(node);
12
+ const dir = black ? "down" : "up";
13
+ if (glows(node)) {
14
+ const glow = defId("glow");
15
+ return (_jsxs(_Fragment, { children: [_jsx("defs", { children: _jsxs("radialGradient", { id: glow, cx: 0, cy: 0, r: 40, gradientUnits: "userSpaceOnUse", children: [_jsx("stop", { offset: 0, stopColor: PALETTE.glow }), _jsx("stop", { offset: 1, stopColor: PALETTE.glow, stopOpacity: 0 })] }) }), _jsx("circle", { r: 48.8, fill: black ? PALETTE.ink : PALETTE.paper, stroke: PALETTE.rim, strokeWidth: 2.4 }), _jsx("polygon", { points: triangle(44, dir), fill: `url(#${glow})`, stroke: PALETTE.rim, strokeWidth: 5, strokeLinejoin: "miter" })] }));
16
+ }
17
+ return (_jsxs(_Fragment, { children: [_jsx("circle", { r: black ? 50 : 48.5, fill: black ? PALETTE.ink : PALETTE.paper, stroke: black ? "none" : PALETTE.ink, strokeWidth: 3 }), _jsx("polygon", { points: triangle(38, dir), fill: black ? PALETTE.paper : PALETTE.ink })] }));
18
+ }
19
+ /** A pillar: black (downward triangle) or white (upward), in 2 × 2 squares. */
20
+ export const pillarArt = {
21
+ Component: Pillar,
22
+ box: { width: 100, height: 100 },
23
+ variant: (node) => variantOf(isBlack(node), glows(node)),
24
+ provenance,
25
+ };
@@ -0,0 +1,8 @@
1
+ export declare const provenance: import("../types.ts").HallArtProvenance;
2
+ /**
3
+ * What the renderer draws for a tag or item kind without art, in the
4
+ * placeholder's 100 × 100 box. The name is untrusted and only ever text.
5
+ */
6
+ export declare function Placeholder({ name }: {
7
+ name: string;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { GD_HALL_FONT_STACKS } from "../fonts.js";
3
+ import { wrapText } from "../labels.js";
4
+ import { original, PALETTE } from "./shared.js";
5
+ export const provenance = original("A dashed box naming a component that has no art.");
6
+ /**
7
+ * What the renderer draws for a tag or item kind without art, in the
8
+ * placeholder's 100 × 100 box. The name is untrusted and only ever text.
9
+ */
10
+ export function Placeholder({ name }) {
11
+ const lines = wrapText(name, 88, 16).slice(0, 3);
12
+ return (_jsxs(_Fragment, { children: [_jsx("rect", { x: -48, y: -48, width: 96, height: 96, fill: PALETTE.paper, fillOpacity: 0.7, stroke: PALETTE.placeholder, strokeWidth: 2, strokeDasharray: "6 4" }), lines
13
+ .map((line, index) => ({
14
+ ...line,
15
+ y: 6 - ((lines.length - 1) * 19) / 2 + index * 19,
16
+ }))
17
+ .map((line) => (_jsx("text", { x: 0, y: line.y, fontSize: 16, fontFamily: GD_HALL_FONT_STACKS.serif, fontStyle: "italic", textAnchor: "middle", fill: PALETTE.placeholder, children: line.text }, line.y)))] }));
18
+ }
@@ -0,0 +1,13 @@
1
+ import type { HallArt, HallArtProps, HallNode } from "../types.ts";
2
+ export declare const provenance: import("../types.ts").HallArtProvenance;
3
+ /** A box `w` × `h` squares, `width` × `height` unless they say otherwise. */
4
+ export declare const sized: (width: number, height: number) => (node: HallNode) => {
5
+ width: number;
6
+ height: number;
7
+ };
8
+ /** The white rectangle every platform, throne and board draws. */
9
+ export declare function Platform({ box }: Pick<HallArtProps, "box">): import("react").JSX.Element;
10
+ /** A raised platform (4 × 3 squares unless w and h say otherwise). */
11
+ export declare const platformArt: HallArt;
12
+ /** A throne (2 × 2 squares unless w and h say otherwise). */
13
+ export declare const throneArt: HallArt;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { clamp, numberAttr } from "../attributes.js";
3
+ import { SIZE_RANGE } from "../vocabulary.js";
4
+ import { original, PALETTE, sameForEveryNode } from "./shared.js";
5
+ export const provenance = original("A white rectangle with a black outline, sized in squares by w and h.");
6
+ /** Outline width, in squares. */
7
+ const STROKE = 0.06;
8
+ /** A box `w` × `h` squares, `width` × `height` unless they say otherwise. */
9
+ export const sized = (width, height) => (node) => ({
10
+ width: clamp(numberAttr(node, "w") ?? width, ...SIZE_RANGE),
11
+ height: clamp(numberAttr(node, "h") ?? height, ...SIZE_RANGE),
12
+ });
13
+ /** The white rectangle every platform, throne and board draws. */
14
+ export function Platform({ box }) {
15
+ return (_jsx("rect", { x: -box.width / 2, y: -box.height / 2, width: box.width, height: box.height, fill: PALETTE.paper, stroke: PALETTE.ink, strokeWidth: STROKE }));
16
+ }
17
+ const art = (width, height) => ({
18
+ Component: Platform,
19
+ box: sized(width, height),
20
+ fit: "squares",
21
+ // The box (w × h) is all it draws, and the box is part of the key.
22
+ variant: sameForEveryNode,
23
+ provenance,
24
+ });
25
+ /** A raised platform (4 × 3 squares unless w and h say otherwise). */
26
+ export const platformArt = art(4, 3);
27
+ /** A throne (2 × 2 squares unless w and h say otherwise). */
28
+ export const throneArt = art(2, 2);
@@ -0,0 +1,23 @@
1
+ import type { HallPlanet } from "../planets.ts";
2
+ /**
3
+ * The planets' seals, drawn after the woodcuts of Agrippa's *De occulta
4
+ * philosophia* (Cologne: Soter, 1533), book II, chapter 22, which gives
5
+ * each planet's table with its seal ("signacula sive characteres"). Read
6
+ * from the Library of Congress copy on archive.org,
7
+ * `DeOccultaPhilosophiaLoc1533`: Venus on p. CLI (leaf n166), the Moon on
8
+ * p. CLIII (leaf n168). The geometry follows the woodcuts, made clean and
9
+ * symmetric where the cutter's hand wandered; no image was copied. The
10
+ * silver plate is our reading of the chapter's text (leaf n162), which
11
+ * says each of these planets' tables, with its characters, is engraved
12
+ * on silver; the woodcuts themselves draw the seals on the bare page.
13
+ * The other five planets keep the placeholder (plan 061, stage H, part
14
+ * 2).
15
+ */
16
+ export declare const SEAL_SOURCE: string;
17
+ export declare const SEAL_BASIS: string;
18
+ /** The `seal` art's provenance: these seals, and the placeholder. */
19
+ export declare const sealProvenance: import("../types.ts").HallArtProvenance;
20
+ /** Every art module exports its provenance. */
21
+ export declare const provenance: import("../types.ts").HallArtProvenance;
22
+ /** The seals drawn so far, by planet; the others are a placeholder. */
23
+ export declare const SEALS: ReadonlyMap<HallPlanet, () => React.ReactElement>;
@@ -0,0 +1,151 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { svgCoordinate } from "../../../svgCoordinate.js";
3
+ import { ITEM_DETAIL, ITEM_OUTLINE, PALETTE, publicDomain } from "./shared.js";
4
+ /**
5
+ * The planets' seals, drawn after the woodcuts of Agrippa's *De occulta
6
+ * philosophia* (Cologne: Soter, 1533), book II, chapter 22, which gives
7
+ * each planet's table with its seal ("signacula sive characteres"). Read
8
+ * from the Library of Congress copy on archive.org,
9
+ * `DeOccultaPhilosophiaLoc1533`: Venus on p. CLI (leaf n166), the Moon on
10
+ * p. CLIII (leaf n168). The geometry follows the woodcuts, made clean and
11
+ * symmetric where the cutter's hand wandered; no image was copied. The
12
+ * silver plate is our reading of the chapter's text (leaf n162), which
13
+ * says each of these planets' tables, with its characters, is engraved
14
+ * on silver; the woodcuts themselves draw the seals on the bare page.
15
+ * The other five planets keep the placeholder (plan 061, stage H, part
16
+ * 2).
17
+ */
18
+ export const SEAL_SOURCE = "Heinrich Cornelius Agrippa, De occulta philosophia libri tres " +
19
+ "(Cologne: Soter, 1533), book II, chapter 22: Venus p. CLI, the Moon " +
20
+ "p. CLIII; archive.org DeOccultaPhilosophiaLoc1533 (Library of " +
21
+ "Congress), leaves n166 and n168.";
22
+ export const SEAL_BASIS = "printed in 1533; Agrippa died in 1535 and the woodcuts' cutters are " +
23
+ "anonymous, so the work is out of copyright everywhere; a faithful scan " +
24
+ "of a two-dimensional public-domain work adds no copyright, and the " +
25
+ "archive.org item carries the Public Domain Mark 1.0. Only the " +
26
+ "figures' geometry is taken, and no image of the book is committed.";
27
+ /** The `seal` art's provenance: these seals, and the placeholder. */
28
+ export const sealProvenance = publicDomain("A planet's seal drawn from geometry after Agrippa's woodcut, on a " +
29
+ "silver plate: the Moon's and Venus' so far (plan 061, stage H, part " +
30
+ "2); for the other planets a placeholder, a dashed ring naming the " +
31
+ "planet.", { source: SEAL_SOURCE, basis: SEAL_BASIS });
32
+ /** Every art module exports its provenance. */
33
+ export const provenance = sealProvenance;
34
+ /** The engraved lines' width, in the 100-unit box. */
35
+ const LINE = 3;
36
+ /** The small rings at the lines' ends. */
37
+ const RING = 3;
38
+ const n = svgCoordinate;
39
+ const pair = ([x, y]) => `${n(x)},${n(y)}`;
40
+ /** A quadratic through `from`, `via` (at its middle) and `to`. */
41
+ const through = (from, via, to) => {
42
+ const control = [
43
+ 2 * via[0] - (from[0] + to[0]) / 2,
44
+ 2 * via[1] - (from[1] + to[1]) / 2,
45
+ ];
46
+ return `M${pair(from)}Q${pair(control)} ${pair(to)}`;
47
+ };
48
+ /**
49
+ * A line from the centre ring out to `to`, from the ring's edge; with
50
+ * `ringed`, it stops at the edge of a ring at `to` too.
51
+ */
52
+ const spoke = (to, ringed = true) => {
53
+ const length = Math.hypot(to[0], to[1]);
54
+ const along = (r) => [
55
+ (to[0] * r) / length,
56
+ (to[1] * r) / length,
57
+ ];
58
+ return `M${pair(along(RING))}L${pair(ringed ? along(length - RING) : to)}`;
59
+ };
60
+ /**
61
+ * A circular arc to `to` from `from`, bowed `depth` from its chord: to the
62
+ * right of the way (as the screen shows it), or with `left`, to the left.
63
+ */
64
+ const bow = (from, to, depth, left = false) => {
65
+ const half = Math.hypot(to[0] - from[0], to[1] - from[1]) / 2;
66
+ const r = (half * half + depth * depth) / (2 * depth);
67
+ return `A${n(r)},${n(r)} 0 0 ${left ? 1 : 0} ${pair(to)}`;
68
+ };
69
+ /** A point turned a quarter turn clockwise about the centre, `k` times. */
70
+ const turn = ([x, y], k) => k % 4 === 0 ? [x, y] : turn([-y, x], k - 1);
71
+ /**
72
+ * The Moon's seal: a saltire from a ring at the centre to rings in the
73
+ * corners, and four crescents between its arms, each a lune with a line
74
+ * along its middle, rings at its horns.
75
+ */
76
+ const MOON_CORNER = 38;
77
+ const MOON_HORN = [23, -38];
78
+ /** How deep each crescent's outer, middle and inner arcs bow. */
79
+ const MOON_CRESCENT = [19, 14.5, 10];
80
+ const MOON = (() => {
81
+ const corners = [0, 1, 2, 3].map((k) => turn([MOON_CORNER, MOON_CORNER], k));
82
+ const lines = corners.map((corner) => spoke(corner));
83
+ const crescents = [0, 1, 2, 3].map((k) => {
84
+ const left = turn([-MOON_HORN[0], MOON_HORN[1]], k);
85
+ const right = turn(MOON_HORN, k);
86
+ const [outer, middle, inner] = MOON_CRESCENT;
87
+ const lune = `M${pair(left)}${bow(left, right, outer)}${bow(right, left, inner, true)}Z`;
88
+ return { lune, middle: `M${pair(left)}${bow(left, right, middle)}` };
89
+ });
90
+ const horns = [0, 1, 2, 3].flatMap((k) => [
91
+ turn([-MOON_HORN[0], MOON_HORN[1]], k),
92
+ turn(MOON_HORN, k),
93
+ ]);
94
+ return {
95
+ lines: lines.join(""),
96
+ lunes: crescents.map(({ lune }) => lune).join(""),
97
+ middles: crescents.map(({ middle }) => middle).join(""),
98
+ rings: [[0, 0], ...corners, ...horns],
99
+ };
100
+ })();
101
+ /**
102
+ * Venus' seal: three arms of a saltire from a ring at the centre to rings
103
+ * at their ends; the fourth runs to the foot of a great arc on the right;
104
+ * above the centre a cross whose stem forks into two arms, serifed at
105
+ * their ends; an open arc on the left, like a ")"; a ring below. Every
106
+ * line is engraved at one weight, the left arc as the great one.
107
+ */
108
+ const VENUS = (() => {
109
+ const ends = [
110
+ [-36, -35],
111
+ [36, -35],
112
+ [-36, 35],
113
+ ];
114
+ const foot = [35, 33];
115
+ const spokes = [...ends.map((end) => spoke(end)), spoke(foot, false)];
116
+ const arc = through([30, -21], [40, 6], foot);
117
+ const leftArc = through([-38, -16], [-27, -3.5], [-38.5, 9]);
118
+ const cross = "M0,-38V-10.5M-13.5,-27H13.5M0,-27L-11.5,-37.5M0,-27L11.5,-37.5" +
119
+ "M-2,-38H2M-14,-37.5H-9.5M9.5,-37.5H14";
120
+ return {
121
+ lines: spokes.join("") + arc + leftArc + cross,
122
+ rings: [[0, 0], ...ends],
123
+ circle: { cx: 0, cy: 25, r: 12.5 },
124
+ };
125
+ })();
126
+ /** A seal's silver plate, the box's size less the outline. */
127
+ function Plate() {
128
+ return (_jsx("rect", { x: -47, y: -47, width: 94, height: 94, rx: 8, fill: PALETTE.silver, ...ITEM_OUTLINE }));
129
+ }
130
+ /** The engraving's strokes: the ink, round at the ends. */
131
+ const ENGRAVED = {
132
+ fill: "none",
133
+ stroke: PALETTE.ink,
134
+ strokeWidth: LINE,
135
+ strokeLinecap: "round",
136
+ strokeLinejoin: "round",
137
+ };
138
+ function Rings({ at }) {
139
+ return (_jsx("g", { fill: PALETTE.silver, stroke: PALETTE.ink, strokeWidth: ITEM_DETAIL, children: at.map(([x, y]) => (_jsx("circle", { cx: n(x), cy: n(y), r: RING }, `${x},${y}`))) }));
140
+ }
141
+ function MoonSeal() {
142
+ return (_jsxs(_Fragment, { children: [_jsx(Plate, {}), _jsx("path", { d: MOON.lines, ...ENGRAVED }), _jsx("path", { d: MOON.lunes, ...ENGRAVED, strokeWidth: ITEM_DETAIL }), _jsx("path", { d: MOON.middles, ...ENGRAVED, strokeWidth: 1.6 }), _jsx(Rings, { at: MOON.rings })] }));
143
+ }
144
+ function VenusSeal() {
145
+ return (_jsxs(_Fragment, { children: [_jsx(Plate, {}), _jsx("path", { d: VENUS.lines, ...ENGRAVED }), _jsx("circle", { ...VENUS.circle, ...ENGRAVED }), _jsx(Rings, { at: VENUS.rings })] }));
146
+ }
147
+ /** The seals drawn so far, by planet; the others are a placeholder. */
148
+ export const SEALS = new Map([
149
+ ["moon", MoonSeal],
150
+ ["venus", VenusSeal],
151
+ ]);
@@ -0,0 +1,87 @@
1
+ import type { HallArtProvenance } from "../types.ts";
2
+ /** Provenance of art drawn for this package from geometry. */
3
+ export declare function original(notes: string): HallArtProvenance;
4
+ /**
5
+ * Provenance of art drawn for this package from geometry after a
6
+ * public-domain original: the drawing (our code and paths) is MIT like
7
+ * the rest, while the design it follows is in the public domain, on the
8
+ * recorded `basis`. `source` cites the original by a stable reference.
9
+ */
10
+ export declare function publicDomain(notes: string, { source, basis }: {
11
+ source: string;
12
+ basis: string;
13
+ }): HallArtProvenance;
14
+ /** `HallArt.variant` for art that draws every node alike. */
15
+ export declare const sameForEveryNode: () => string;
16
+ /** `HallArt.variant` from the normalised values a drawing reads. */
17
+ export declare const variantOf: (...values: readonly (string | number | boolean | undefined)[]) => string;
18
+ /** The hall's palette (plan 061, decision 13: light only for now). */
19
+ export declare const PALETTE: {
20
+ readonly ink: "#000000";
21
+ readonly paper: "#ffffff";
22
+ readonly checkerLight: "#f9f9f9";
23
+ readonly checkerDark: "#ececec";
24
+ readonly rim: "#cccccc";
25
+ readonly glow: "#ff0000";
26
+ readonly bannerDark: "#252524";
27
+ readonly bannerLightFrom: "#e0e0e0";
28
+ readonly bannerLightTo: "#ffffff";
29
+ readonly knob: "#ffe600";
30
+ readonly tassel: "#d92128";
31
+ readonly gold: "#fae513";
32
+ readonly water: "#406ab3";
33
+ readonly fire: "#d92128";
34
+ readonly tau: "#f9f9f9";
35
+ readonly placeholder: "#888888";
36
+ readonly veil: "#c8b8e0";
37
+ readonly veilPleat: "#7a64a0";
38
+ readonly bronze: "#b08d3c";
39
+ readonly flame: "#ff9a1f";
40
+ readonly wax: "#fffbe8";
41
+ /** Gold's deep tone: rims, knops and bands on gold. */
42
+ readonly goldDeep: "#d4a017";
43
+ readonly silver: "#d3d7dc";
44
+ readonly wine: "#7a0c1e";
45
+ readonly bread: "#e3a857";
46
+ /** Where a loaf's crust opens. */
47
+ readonly crumb: "#f6d79a";
48
+ readonly green: "#108010";
49
+ readonly rose: "#d92128";
50
+ /** Air's yellow: the fan. */
51
+ readonly air: "#ffe000";
52
+ /** A light's glass, by the colour of its light. */
53
+ readonly glassRed: "#ff9a8a";
54
+ readonly glassYellow: "#fbf0b8";
55
+ readonly glassWhite: "#ffffff";
56
+ readonly citrine: "#a9aa66";
57
+ readonly olive: "#444f41";
58
+ readonly russet: "#8f5642";
59
+ readonly earthBlack: "#2c2b29";
60
+ /** The Third Adept's octagram. */
61
+ readonly orange: "#ff8c1a";
62
+ /** Typhon's storm cloud (stage H, part 2). */
63
+ readonly storm: "#6b6f78";
64
+ };
65
+ /**
66
+ * The items' one outline (plan 061, stage H): black, 3.6 units in a
67
+ * drawing 100 units on its longer side, round joins. Set on a drawing's
68
+ * group, so its shapes give only their fills; inner lines are
69
+ * `ITEM_DETAIL` wide.
70
+ */
71
+ export declare const ITEM_OUTLINE: {
72
+ readonly stroke: "#000000";
73
+ readonly strokeWidth: 3.6;
74
+ readonly strokeLinejoin: "round";
75
+ readonly strokeLinecap: "round";
76
+ };
77
+ /** The width of the few lines drawn inside an item's outline. */
78
+ export declare const ITEM_DETAIL = 2.4;
79
+ /**
80
+ * The one white highlight a metal item carries (direction C's, kept flat):
81
+ * a stroke along the lit side of the form, drawn over the fill.
82
+ */
83
+ export declare const HIGHLIGHT: {
84
+ readonly fill: "none";
85
+ readonly stroke: "#ffffff";
86
+ readonly strokeWidth: 2.6;
87
+ };