@johnmorrisdotca/kyogijo-stadiums 0.6.2 → 0.8.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 (57) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +2 -2
  3. package/dist/budo/looks.js +1 -1
  4. package/dist/budo/room.d.ts +34 -2
  5. package/dist/budo/room.js +39 -7
  6. package/dist/budo/types.d.ts +6 -1
  7. package/dist/budo.style.d.ts +1 -1
  8. package/dist/budo.style.js +6 -3
  9. package/dist/charts-entry.d.ts +0 -3
  10. package/dist/charts-entry.js +1 -3
  11. package/dist/crowd.d.ts +4 -0
  12. package/dist/crowd.draw.d.ts +5 -0
  13. package/dist/crowd.draw.js +34 -5
  14. package/dist/crowd.js +6 -1
  15. package/dist/draw.js +6 -3
  16. package/dist/element.d.ts +3 -3
  17. package/dist/element.js +16 -2
  18. package/dist/halls.js +4 -3
  19. package/dist/layout.js +1 -2
  20. package/dist/mark.d.ts +1 -1
  21. package/dist/mark.js +1 -1
  22. package/dist/strings.d.ts +7 -29
  23. package/dist/strings.js +6 -24
  24. package/dist/style.js +1 -2
  25. package/dist/surfaces/budo.js +22 -2
  26. package/dist/turn-entry.d.ts +2 -0
  27. package/dist/turn-entry.js +1 -0
  28. package/dist/turn.dom.d.ts +8 -0
  29. package/dist/turn.dom.js +147 -8
  30. package/dist/turn.kit.d.ts +5 -3
  31. package/dist/turn.kit.js +1 -1
  32. package/dist/turn.markup.d.ts +8 -1
  33. package/dist/turn.markup.js +18 -4
  34. package/dist/turn.model.d.ts +26 -0
  35. package/dist/turn.model.js +34 -3
  36. package/dist/turn.style.js +58 -4
  37. package/dist/uprights.d.ts +7 -0
  38. package/dist/uprights.js +15 -1
  39. package/dist/venue.d.ts +31 -0
  40. package/dist/venue.geom.d.ts +53 -0
  41. package/dist/venue.geom.js +225 -0
  42. package/dist/venue.js +172 -0
  43. package/dist/venue.room.d.ts +9 -0
  44. package/dist/venue.room.js +240 -0
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.js +1 -1
  47. package/package.json +1 -1
  48. package/dist/road.d.ts +0 -26
  49. package/dist/road.draw.d.ts +0 -6
  50. package/dist/road.draw.js +0 -270
  51. package/dist/road.js +0 -60
  52. package/dist/road.model.d.ts +0 -63
  53. package/dist/road.model.js +0 -215
  54. package/dist/road.style.d.ts +0 -7
  55. package/dist/road.style.js +0 -26
  56. package/dist/road.types.d.ts +0 -125
  57. package/dist/road.types.js +0 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-10-14
11
+
12
+ ### Added
13
+
14
+ - **The venue in the 3D view** ([docs/turn.md](./docs/turn.md#the-venue)): the stands, roofs and rooms round a surface are raised from the data the package already has, so that a whole stadium, ballpark, arena or dojo turns by mouse drag, finger, keys and buttons. Every tier of `crowdStands()` is a strip of raked planes (a quad to each straight side, three to each corner, a ballpark's points, 28 round an oval) from the height of its first row to its last, with the crowd's own pattern as texture (people, empty seats, row lines, aisles, the visitors' block, the tifo), the walks between the tiers, a low wall under the first row, and the places the stands are left open left open. A ballpark gets its grandstands and bleachers and, where the park has them, the seats on top of the wall (Fenway Park's Green Monster at 37 ft 2 in). A dojo gets its cream walls over a wood wainscot, painted in the floor's own look, with the doors and windows of `dojoRoom()`, the dais, the banner, the shrine, the balconies as steps of seats behind a rail with the crowd in them, overlooking the floor, and the thickness of the mats at their edge. `venue: true | false | { roof, detail }` is the switch: on when the drawing has a `crowd` or is a dojo, off with `uprights: false`; `roof: true` adds a canopy over the top tier or a lid over a hall. The stands and walls fade on the side nearest the eye as the boards do, so the surface is never hidden. A full soccer bowl is about 110 planes and 500 elements and the biggest venues under 1,000, whatever the seats taken; `detail` cuts the curves coarser or finer. The element reads `turn-venue="false"` and `turn-roof`; the demo has a Roof toggle in 3D.
15
+ - **An auto-turn for the 3D view**, a trophy case's slow turn: an Auto-turn toggle button (`aria-pressed`, 44 pixels or more, in English and Japanese) and Space start and stop it; `autoPlay` (`true`, or `"force"`) is the opt-in start, `speed` its degrees a second, `onPlayChange` its callback, and the handle has `playing`, `play()`, `pause()` and `togglePlay()`. It holds while the pointer holds the view, does nothing on a hidden page or off the screen, is stopped by the arrow keys, a viewpoint, the reset or `set`, starts paused for a reader who prefers reduced motion, and moves nothing until Play is pressed unless `autoPlay` asks. The element reads `turn-autoplay` and `turn-speed`.
16
+
17
+ ### Changed
18
+
19
+ - **The Kodokan's gallery is as its owner counts it, in 2D and 3D.** `dojo.balcony` takes `rows` and `sections` (the seats in each block of a row, with an aisle between blocks, the same on every side): the main dojo has `{ rows: 3, sections: [5, 8, 8, 8, 5] }`, 34 seats to a row and 102 to a side, per the owner and his photographs. `dojoRoom()`'s balconies carry `sections`, `seatWidth`, `aisle`, `aisles` (the shares of a side the aisles lie at) and `sectionsBasis`, and `dojoGallery()` returns `sections` and `aisles`; a side with no `sections` is cut into blocks of about 9 m (ours). The gallery is 3 rows deep by default and its seats are empty: a dojo's crowd density defaults to 0 and people are drawn only when `density` or `attendance` asks. In 2D the aisles lie across the band and each row is dashed cushions with a darker line of backs; in 3D the seats are dark brown cushions and backs in blocks on dark treads, the aisles lighter wood with their stairs, a white rail, honey wood panelling on the walls under the gallery, plain off-white above it with a clock, two doors and three windows, the kamiza's alcove with a framed shrine, a scroll and a plain purple banner, and, with `roof: true`, a pale ceiling band with box lights. The surround of a dojo (when it has one) is polished hardwood: the basketball floor's boards and seams, at half strength, under a warm satin sheen.
20
+
21
+ - **A dojo's gallery is a proper balcony in 3D and neat seat rows in 2D.** In 3D each balcony has a floor slab that projects over the room (with its thickness showing), a white rail with two bars and posts on its edge, and four tiers of seats drawn as cushions and backs, a seat every 0.6 m, on light treads. In 2D, from above, the gallery is rows of seats along its band (a dashed line to a row, in the gallery's brown) and not a ring of round heads; `crowd` still gives the people in the 3D balcony. The Kodokan's four areas are the IJF's block (8 m areas, 4 m shared safety between them, 3.2 m round in the 26.4 m square of 420 jō): the pale areas are 256 m² of 697.
22
+
23
+ - **The mats look like mats, in 2D and 3D.** The seams between tatami are drawn a few per cent darker than the mat (ink at 12%, they were 26%), the weave is dark ridges 3% deep at most (it had white ridges 14% deep), the faint light line on the mat edges is a 4% dark one, and the heri, which drew heavy dark bands between the mats, is as faint as a seam unless `colours.heri` names a colour (`--ky-bud-hop` then restores its 0.82). A contest area is just another colour of mat. The walls of every look are a light cream, not a dark grey. This is the drawing of the dojo, judo, karate and kendo themselves, so every drawing of them changes (their fingerprints in the identity test are made again).
24
+ - **The dojo's room is a storey high and its gallery is on the upper level.** A dojo with a gallery has walls 4.5 m high, the gallery's floor level with their tops (it was 3 m walls under a gallery at 4 m), the gallery as steps of seats behind a white rail, three large windows high in the wall behind it on the east side, and, with `roof: true`, a pale ceiling over it; `dojoRoom()` gives `upperWindows` and `ceiling` (all ours). The Kodokan's main dojo has a polished wood floor 4 m wide all round the mats (it was 1 m), so its walls stand far out.
25
+
26
+ - A 3D view with a crowd no longer lays the flat crowd on its floor: the stands are planes, and the floor is the surface with the ground they stand on. `venue: false` gives the flat crowd back. The crowd's `patterns()` take an optional row-lines argument (the 2D drawing is byte for byte as it was).
27
+
28
+ ## [0.7.0] - 2026-10-14
29
+
30
+ ### Removed
31
+
32
+ - **`roadToFinal` and the road-to-the-final chart moved to [`@johnmorrisdotca/taisen-brackets`](https://www.npmjs.com/package/@johnmorrisdotca/taisen-brackets)** (0.4.0 and later; its `/road` entry), where it is built on the bracket package and shares its look. There is no shim here: import it from taisen. Gone from `@johnmorrisdotca/kyogijo-stadiums/charts`: `roadToFinal`, `ROAD_SIZES`, `KYOGIJO_ROAD_STYLE` (and from `KYOGIJO_STYLE`) and the types `RoadChart`, `RoadDecided`, `RoadFinal`, `RoadFinalist`, `RoadGroupMatch`, `RoadMatch`, `RoadOptions`, `RoadRecord`, `RoadScore`, `RoadSize`, `RoadSizeInfo` and `RoadTotals`. The `road` words are gone from `KYOGIJO_STRINGS` (a custom set of words no longer has to give them), and the docs' section, the demo's "Road to the final" section, its gallery items and its tests went with it.
33
+ - **The round mark stays.** `Team.mark`, `markShape` and `KYOGIJO_MARK_STYLE` are for surfaces and are unchanged. The demo's section for them is now "Round marks" (a pitch wide and tall, and a badge), with its own sixteen made-up nations in `demo/nations.js`, the same flags as before.
34
+
10
35
  ## [0.6.2] - 2026-10-14
11
36
 
12
37
  ### Fixed
package/README.md CHANGED
@@ -98,10 +98,10 @@ console.log(svg.startsWith("<svg"), svg.includes("Northern Owls 3, Lakeshore Anc
98
98
  - **Wide, tall or fit the box.** Every surface lies wide or tall: tall turns it a quarter, keeps every word upright and puts the teams top and bottom. With `orientation: "auto"` the browser helpers follow the box.
99
99
  - **Where a point lies, by the rules, and a click as a place.** `zoneAt("nhl", [69, 22], { frame: "nhl-api" })` names the zone and the area in it from the rink's, court's, pitch's, park's or field's own figures (the offensive zone and its face-off circle, the frontcourt and the corner three, the attacking third and the six-yard box, the infield and the warning track, the red zone and between the hashes), with `isThreePointer`, `inSlot`, `inPenaltyArea`, `isFair`, `overWall` and the like, and `isHomeRun(park, point, { height })` from the park's fence and wall; `clientPointToField(svg, x, y, { provider })` is a pointer's place in any of the frames, with the zone, and `fieldPointToClient` the way back. `marks: true` writes `data-mark-id`, `data-index` and `data-zone` on every mark of a layer, for one listener to link it to a table. [Zones](./docs/zones.md), [pointing](./docs/pointer.md).
100
100
  - **Goals, the strike zone and the stumps, end-on.** A goal mouth for hockey (6 by 4 feet), soccer, handball, water polo, lacrosse (6 by 6) and field hockey with the shots laid on it and its zones (the top shelf, the five-hole, the corners), the catcher's view of the strike zone with its nine and thirteen zones and the pitches numbered, and the wicket from the bowler's end with its line and length zones. [Targets](./docs/targets.md).
101
- - **Data on the surface.** Points, lines, arrows, text, heat maps, hexagons, zone charts and density contours over any surface, in its feet or a data provider's frame, cut to its outline, with a table for each layer; ready-made shot and pass charts, and the road to a tournament's final (`roadToFinal`: the two finalists' matches as round flags with the scores, in four sizes). [Data layers](./docs/layers.md), [charts](./docs/charts.md).
101
+ - **Data on the surface.** Points, lines, arrows, text, heat maps, hexagons, zone charts and density contours over any surface, in its feet or a data provider's frame, cut to its outline, with a table for each layer; ready-made shot and pass charts (the road to a tournament's final moved to [`@johnmorrisdotca/taisen-brackets`](https://www.npmjs.com/package/@johnmorrisdotca/taisen-brackets), its `/road` entry). [Data layers](./docs/layers.md), [charts](./docs/charts.md).
102
102
  - **Small multiples and backgrounds.** `drawGrid` puts many drawings in one SVG, under a title and a subtitle, over a credit line, with a key they share, in a size for a social card, a square post or a phone; `background` lays a quiet pattern (felt, grass, wood, ice, paper, a grid, dots) or a photograph behind a drawing or a grid. [Layouts and backgrounds](./docs/layouts.md).
103
103
  - **Replay.** Tracking data played on any surface: the players with their numbers, the ball and its trail, and the passes, shots and goals as they happen, with controls, the keyboard and a live region, steps for a reader who prefers reduced motion, and a pause when the page is hidden. [Replay](./docs/replay.md).
104
- - **A 3D view.** Any surface as a floor that turns by mouse, touch, wheel or keys, with four viewpoints (overhead, broadcast, end-on, low corner) and a reset, and what the rulebook stands on it at its height: the net and antennae of volleyball, the nets of tennis, badminton, pickleball and table tennis, baskets and stanchions, rink boards and glass, goals, the posts of football, rugby, Gaelic and Australian football, stumps, a ballpark's fence, the walls of padel, squash and racquetball (cut away on the side nearest the eye), a swimming pool's blocks and flags, a diving well's boards and platforms, a water polo goal, a jump's uprights and bar, a throwing cage, the hurdles, and the padding of a skating track. Plain DOM and CSS over the same SVG, flat where 3D is not drawn. [The 3D view](./docs/turn.md).
104
+ - **A 3D view.** Any surface as a floor that turns by mouse, touch, wheel or keys, with four viewpoints (overhead, broadcast, end-on, low corner) and a reset, and what the rulebook stands on it at its height: the net and antennae of volleyball, the nets of tennis, badminton, pickleball and table tennis, baskets and stanchions, rink boards and glass, goals, the posts of football, rugby, Gaelic and Australian football, stumps, a ballpark's fence, the walls of padel, squash and racquetball (cut away on the side nearest the eye), a swimming pool's blocks and flags, a diving well's boards and platforms, a water polo goal, a jump's uprights and bar, a throwing cage, the hurdles, and the padding of a skating track; and the venue round it, raised from the same data (the raked stands of a `crowd` with the people on them, a ballpark's grandstands and the seats on its wall, roofs on request, a dojo's walls, dais, banner and gallery), faded on the side nearest the eye, with an auto-turn toggle. Plain DOM and CSS over the same SVG, flat where 3D is not drawn. [The 3D view](./docs/turn.md).
105
105
  - **Renderers.** The same drawing as plain data (a scene), on a canvas (a browser's or a Node canvas), in a d3 selection, as a mark of an Observable Plot, as GeoJSON and as a PNG in Node, each an entry of its own that imports nothing from outside the package. [Renderers](./docs/renderers.md).
106
106
  - **Units and frames.** Each surface in its rulebook's unit and any other, and 48 coordinate frames (ten soccer providers, the NHL API, HockeyTech, NBA stats, NFL tracking, MLBAM, Retrosheet, a curling stone's place from the button, a place from the net in each net sport (from the centre of the court in squash, racquetball, roundnet and netball), the centre of each field sport's surface, the middle of the water of each pool, the middle of the athletics stadium and of each skating rink) to move a provider's points onto a surface. [Units](./docs/units.md), [coordinate frames](./docs/frames.md).
107
107
  - **A compact badge and a compact card.** One strip for a list, or the surface with that strip under it.
@@ -15,7 +15,7 @@ function pair(light, k = 0.34) {
15
15
  return { light, dark };
16
16
  }
17
17
  const FLOOR = "#c9a26a";
18
- const WALLS = "#7d756a";
18
+ const WALLS = "#d8d0bd";
19
19
  const COMMON = { apron: "#d6d3cb", free: "#e8e4d8", floor: FLOOR, boards: WALLS };
20
20
  const MATS = {
21
21
  "yellow-red": pair({ ...COMMON, surface: "#f4c430", alt: "#c9343b", red: "#b32a2f" }),
@@ -33,6 +33,16 @@ export interface RoomBalcony {
33
33
  rowRise: number;
34
34
  /** The height of the rail along the front edge. */
35
35
  rail: number;
36
+ /** The seats in each block of a row, from one end of the side to the other: an aisle lies between one block and the next. The same in every row. */
37
+ sections: number[];
38
+ /** How wide a seat is, in metres: the side's length less its aisles, over its seats (a seat is 0.6 m or a little more). */
39
+ seatWidth: number;
40
+ /** The width of an aisle, in metres (ours). */
41
+ aisle: number;
42
+ /** Where the aisles' middles lie along the side, as shares of its length from 0 to 1. */
43
+ aisles: number[];
44
+ /** Whether the blocks of seats are the options' (`given`) or cut by us from the side's length. */
45
+ sectionsBasis: "given" | "ours";
36
46
  }
37
47
  /** The room round a budō surface. */
38
48
  export interface DojoRoom {
@@ -74,6 +84,19 @@ export interface DojoRoom {
74
84
  balconies: RoomBalcony[];
75
85
  doors: RoomOpening[];
76
86
  windows: RoomOpening[];
87
+ /** The windows high in the wall behind a gallery (a clerestory): the side, the stretch along the wall from its start (metres), the sill and the head, ours. */
88
+ upperWindows: {
89
+ side: Compass;
90
+ from: number;
91
+ to: number;
92
+ sill: number;
93
+ head: number;
94
+ }[];
95
+ /** How high the room goes above a gallery's floor, for a ceiling over the gallery, and the height of that floor: undefined for a room with no gallery (ours). */
96
+ ceiling: {
97
+ gallery: number;
98
+ height: number;
99
+ } | undefined;
77
100
  /** The shrine of a dojo (the shinzen), with its height. */
78
101
  shrine: {
79
102
  box: MBox;
@@ -94,6 +117,9 @@ export interface DojoGallery {
94
117
  offset: number;
95
118
  depth: number;
96
119
  rows: number;
120
+ /** The seats in each block of a row, and where the aisles lie along a side (shares of its length). */
121
+ sections: number[];
122
+ aisles: number[];
97
123
  height: [number, number];
98
124
  }
99
125
  /** Our heights, in metres, where no source gives one. */
@@ -107,13 +133,19 @@ export declare const ROOM_HEIGHTS: Readonly<{
107
133
  windowHead: 2.4;
108
134
  shrine: 1.2;
109
135
  rail: 1;
110
- balcony: 4;
136
+ balcony: 4.5;
137
+ ceiling: 4.5;
111
138
  mat: 0.05;
112
139
  rowRise: 0.4;
113
140
  rowDepth: 0.8;
114
141
  balconyDepth: 2.4;
115
- balconyRows: 4;
142
+ balconyRows: 3;
143
+ aisle: 1.2;
144
+ seat: 0.6;
145
+ blockLength: 9.14;
116
146
  }>;
147
+ /** The blocks of a row along a side `length` metres long: the options' `given`, else as many as a block of about 9 m allows (two at least), each as many seats as fit at 0.6 m with the aisles between. */
148
+ export declare function sectionsFor(length: number, given?: readonly number[]): number[];
117
149
  /**
118
150
  * The room of a budō surface as data: what a 3D view raises. A dojo has its walls (3 m, ours), wood surround, dais, banner, balconies, doors, windows and shrine as its spec asks for them; the other
119
151
  * surfaces have what they have (the mats' thickness, the dohyō's platform at its height, the Budokan's octagon). Heights that no source gives are ours and are listed in `notes`.
package/dist/budo/room.js CHANGED
@@ -1,7 +1,15 @@
1
1
  import { dojoSpecOf } from "./dojo.js";
2
2
  import { BUDOKAN_ARENA, budoPlan } from "./plan.js";
3
3
  /** Our heights, in metres, where no source gives one. */
4
- export const ROOM_HEIGHTS = Object.freeze({ wall: 3, dais: 0.3, bannerBottom: 1, bannerTop: 3, door: 2, windowSill: 1, windowHead: 2.4, shrine: 1.2, rail: 1, balcony: 4, mat: 0.05, rowRise: 0.4, rowDepth: 0.8, balconyDepth: 2.4, balconyRows: 4 });
4
+ export const ROOM_HEIGHTS = Object.freeze({ wall: 3, dais: 0.3, bannerBottom: 1, bannerTop: 3, door: 2, windowSill: 1, windowHead: 2.4, shrine: 1.2, rail: 1, balcony: 4.5, ceiling: 4.5, mat: 0.05, rowRise: 0.4, rowDepth: 0.8, balconyDepth: 2.4, balconyRows: 3, aisle: 1.2, seat: 0.6, blockLength: 9.14 });
5
+ /** The blocks of a row along a side `length` metres long: the options' `given`, else as many as a block of about 9 m allows (two at least), each as many seats as fit at 0.6 m with the aisles between. */
6
+ export function sectionsFor(length, given) {
7
+ if (given !== undefined && given.length > 0 && given.every((k) => Number.isInteger(k) && k > 0))
8
+ return [...given];
9
+ const blocks = Math.max(2, Math.round(length / ROOM_HEIGHTS.blockLength));
10
+ const seats = Math.max(1, Math.floor((length - (blocks - 1) * ROOM_HEIGHTS.aisle) / blocks / ROOM_HEIGHTS.seat));
11
+ return Array.from({ length: blocks }, () => seats);
12
+ }
5
13
  const SIDES = ["north", "east", "south", "west"];
6
14
  const COMPASS = { north: "north", south: "south", east: "east", west: "west", top: "north", bottom: "south", left: "west", right: "east" };
7
15
  /**
@@ -24,7 +32,7 @@ export function dojoRoom(o) {
24
32
  const mv = (b) => ({ x0: b.x0 - ox, y0: b.y0 - oy, x1: b.x1 - ox, y1: b.y1 - oy });
25
33
  const floor = mv(plan.box);
26
34
  const notes = [];
27
- const room = { sport: o.sport, unit: "m", floor, mats: undefined, surround: undefined, walls: [], platforms: [], banners: [], balconies: [], doors: [], windows: [], shrine: undefined, hall: undefined, notes };
35
+ const room = { sport: o.sport, unit: "m", floor, mats: undefined, surround: undefined, walls: [], platforms: [], banners: [], balconies: [], doors: [], windows: [], upperWindows: [], ceiling: undefined, shrine: undefined, hall: undefined, notes };
28
36
  if (plan.judo !== undefined) {
29
37
  const fig = plan.figures;
30
38
  room.mats = { thickness: fig.tatami.thickness, edge: "rounded", basis: "source" };
@@ -35,6 +43,10 @@ export function dojoRoom(o) {
35
43
  room.mats = { thickness: ROOM_HEIGHTS.mat, edge: "square", basis: "ours" };
36
44
  if (L.surround > 0)
37
45
  room.surround = { outer: mv(L.room), inner: mv(L.floor), width: L.surround, material: "wood" };
46
+ // A dojo with a gallery is a storey high: the gallery's floor is level with the top of the walls, and the walls are as high as the gallery's floor (4.5 m, ours).
47
+ const gallery = spec.balcony !== undefined && spec.balcony !== false;
48
+ const galleryOwn = typeof spec.balcony === "object" && spec.balcony !== null ? spec.balcony : {};
49
+ const wallHeight = gallery ? galleryOwn.height ?? ROOM_HEIGHTS.balcony : ROOM_HEIGHTS.wall;
38
50
  if (L.wall > 0) {
39
51
  const r = mv(L.room);
40
52
  const w = L.wall;
@@ -45,8 +57,8 @@ export function dojoRoom(o) {
45
57
  ["west", [r.x0 - w / 2, r.y1 + w / 2], [r.x0 - w / 2, r.y0 - w / 2]],
46
58
  ];
47
59
  for (const [side, from, to] of ring)
48
- room.walls.push({ id: `wall-${side}`, side, from, to, thickness: w, height: ROOM_HEIGHTS.wall, basis: "ours" });
49
- notes.push(`The walls are ${ROOM_HEIGHTS.wall} m high (ours).`);
60
+ room.walls.push({ id: `wall-${side}`, side, from, to, thickness: w, height: wallHeight, basis: "ours" });
61
+ notes.push(`The walls are ${wallHeight} m high (ours)${gallery ? ", a storey: the gallery's floor is level with their tops" : ""}.`);
50
62
  }
51
63
  if (L.dais !== undefined) {
52
64
  room.platforms.push({ id: "dais", box: mv(L.dais), height: ROOM_HEIGHTS.dais, basis: "ours" });
@@ -70,9 +82,29 @@ export function dojoRoom(o) {
70
82
  for (const side of sides) {
71
83
  const box = side === "north" ? { x0: outer.x0 - depth, y0: outer.y0 - depth, x1: outer.x1 + depth, y1: outer.y0 } : side === "south" ? { x0: outer.x0 - depth, y0: outer.y1, x1: outer.x1 + depth, y1: outer.y1 + depth } : side === "west" ? { x0: outer.x0 - depth, y0: outer.y0, x1: outer.x0, y1: outer.y1 } : { x0: outer.x1, y0: outer.y0, x1: outer.x1 + depth, y1: outer.y1 };
72
84
  const rows = own.rows ?? ROOM_HEIGHTS.balconyRows;
73
- room.balconies.push({ side, box, height: own.height ?? ROOM_HEIGHTS.balcony, depth, rows, rowDepth: depth / rows, rowRise: ROOM_HEIGHTS.rowRise, rail: ROOM_HEIGHTS.rail });
85
+ // The blocks of seats along the side, the seats spread over its length with an aisle between the blocks.
86
+ const length = side === "north" || side === "south" ? box.x1 - box.x0 : box.y1 - box.y0;
87
+ const sections = sectionsFor(length, own.sections);
88
+ const seats = sections.reduce((a, k) => a + k, 0);
89
+ const aisle = ROOM_HEIGHTS.aisle;
90
+ const seatWidth = (length - (sections.length - 1) * aisle) / seats;
91
+ let run = 0;
92
+ const aisles = sections.slice(0, -1).map((k, i) => {
93
+ run += k * seatWidth;
94
+ return (run + i * aisle + aisle / 2) / length;
95
+ });
96
+ room.balconies.push({ side, box, height: own.height ?? ROOM_HEIGHTS.balcony, depth, rows, rowDepth: depth / rows, rowRise: ROOM_HEIGHTS.rowRise, rail: ROOM_HEIGHTS.rail, sections, seatWidth, aisle, aisles, sectionsBasis: own.sections === undefined ? "ours" : "given" });
97
+ }
98
+ // The windows above the gallery, on the east side (ours): three tall panes high in the wall behind its last row.
99
+ const east = room.balconies.find((x) => x.side === "east");
100
+ if (east !== undefined) {
101
+ const along = east.box.y1 - east.box.y0;
102
+ const top = east.height + east.rows * east.rowRise + east.rail;
103
+ for (let i = 0; i < 3; i += 1)
104
+ room.upperWindows.push({ side: "east", from: along * (0.12 + i * 0.29), to: along * (0.12 + i * 0.29 + 0.25), sill: top + 0.4, head: top + ROOM_HEIGHTS.ceiling - 0.6 });
74
105
  }
75
- notes.push("The balcony's height, depth and rows are ours; where a source gives its seats (the Kodokan's gallery, about 900) the atlas hall says so.");
106
+ room.ceiling = { gallery: own.height ?? ROOM_HEIGHTS.balcony, height: (own.height ?? ROOM_HEIGHTS.balcony) + (own.rows ?? ROOM_HEIGHTS.balconyRows) * ROOM_HEIGHTS.rowRise + ROOM_HEIGHTS.rail + ROOM_HEIGHTS.ceiling };
107
+ notes.push("The balcony's height (4.5 m, a storey), depth and its 3 rows are ours, as are the windows above it and the ceiling, 4.5 m over its last row; where a source gives its seats (the Kodokan's gallery, about 900) the atlas hall says so.");
76
108
  }
77
109
  }
78
110
  if (plan.sumo !== undefined)
@@ -107,5 +139,5 @@ export function dojoGallery(o) {
107
139
  return undefined;
108
140
  const f = room.floor;
109
141
  const offset = Math.min(...room.balconies.map((x) => (x.side === "north" ? f.y0 - x.box.y1 : x.side === "south" ? x.box.y0 - f.y1 : x.side === "west" ? f.x0 - x.box.x1 : x.box.x0 - f.x1)));
110
- return { offset, depth: b.depth, rows: b.rows, height: [b.height, b.height + b.rows * b.rowRise] };
142
+ return { offset, depth: b.depth, rows: b.rows, sections: b.sections, aisles: b.aisles, height: [b.height, b.height + b.rows * b.rowRise] };
111
143
  }
@@ -168,12 +168,17 @@ export interface DojoSpec {
168
168
  texture?: "weave" | "none";
169
169
  /** The wood round the mats, between them and the walls, in metres (none by default; the Kodokan's main dojo has a polished wood surround). */
170
170
  surround?: number;
171
- /** A spectators' balcony above the floor, as `dojoRoom()` gives it (it is not part of the plan's drawing; `crowd` draws the stands): `true` for all four sides, or the sides, depth in metres (3 by default), seat rows (5) and height above the mats (4 m). */
171
+ /**
172
+ * A spectators' balcony above the floor, a storey up, as `dojoRoom()` gives it (it is not part of the plan's drawing; `crowd` draws the stands): `true` for all four sides, or the sides, depth in metres (2.4 by
173
+ * default), seat rows (3) and height above the mats (4.5 m). `sections` is the number of seats in each block of a row, from one end of a side to the other, with an aisle between blocks (the same on every side): `[5, 8, 8, 8, 5]`
174
+ * is the Kodokan's, 34 to a row; without it a side is cut into blocks of about 9 m. The seats are spread over the side's length, so a seat is as wide as that makes it.
175
+ */
172
176
  balcony?: boolean | {
173
177
  sides?: readonly Side4[];
174
178
  depth?: number;
175
179
  rows?: number;
176
180
  height?: number;
181
+ sections?: readonly number[];
177
182
  };
178
183
  /** The seat of honour's platform: a raised wooden dais at the middle of the kamiza wall (a chair stands on it in the Kodokan's main dojo). Off by default. */
179
184
  dais?: boolean;
@@ -1,5 +1,5 @@
1
1
  /** The rules of a dojo's gallery, the stands of a crowd in a dojo: brown seats and a rail, a few neutral people. */
2
- export declare const KYOGIJO_BUDO_GALLERY_STYLE = "\n.kyogijo .ky-bud-gbed.ky-bud-gbed{fill:var(--ky-bud-gal,#7a5a3c)}\n.kyogijo .ky-bud-gblk.ky-bud-gblk{fill:var(--ky-bud-gpeople,#a89a86)}\n.kyogijo .ky-bud-gseat.ky-bud-gseat{stroke:var(--ky-bud-grail,#3a2c1f);stroke-opacity:.3}\n.kyogijo .ky-bud-rail{stroke:var(--ky-bud-grail,#3a2c1f);stroke-width:1.6;stroke-opacity:.85}\n";
2
+ export declare const KYOGIJO_BUDO_GALLERY_STYLE = "\n.kyogijo .ky-bud-gbed.ky-bud-gbed{fill:var(--ky-bud-gal,#7a5a3c)}\n.kyogijo .ky-bud-gblk.ky-bud-gblk{fill:var(--ky-bud-gpeople,#a89a86)}\n.kyogijo .ky-bud-gseat.ky-bud-gseat{stroke:var(--ky-bud-grail,#3a2c1f);stroke-opacity:.3}\n.kyogijo .ky-bud-grow{stroke:var(--ky-bud-grail,#3a2c1f);stroke-opacity:.42}\n.kyogijo .ky-bud-gback{stroke:var(--ky-bud-grail,#3a2c1f);stroke-opacity:.75}\n.kyogijo .ky-bud-gaisle{stroke:var(--ky-bud-gaisle,#a98863)}\n.kyogijo .ky-bud-rail{stroke:var(--ky-bud-grail,#3a2c1f);stroke-width:1.6;stroke-opacity:.85}\n";
3
3
  /** The rules of a drawing of a budō sport in a look: the shared rules and the look's colours. Nothing for another sport. */
4
4
  export declare function budoStyle(sport: string, look?: string): string;
5
5
  /** Every budō sport's rules and every look, for a page that draws with `style: "none"` and supplies the stylesheet itself. */
@@ -5,8 +5,8 @@ const CLASSES = `
5
5
  .kyogijo .ky-bud-t1{fill:var(--ky-surface)}
6
6
  .kyogijo .ky-bud-t2{fill:var(--ky-surface-alt,var(--ky-surface))}
7
7
  .kyogijo .ky-bud-t3{fill:var(--ky-red)}
8
- .kyogijo .ky-bud-heri{fill:var(--ky-bud-heri,var(--ky-ink));fill-opacity:.82}
9
- .kyogijo .ky-bud-seam{stroke:var(--ky-ink);stroke-opacity:.26;fill:none;stroke-linejoin:round}
8
+ .kyogijo .ky-bud-heri{fill:var(--ky-bud-heri,var(--ky-ink));fill-opacity:var(--ky-bud-hop,.07)}
9
+ .kyogijo .ky-bud-seam{stroke:var(--ky-ink);stroke-opacity:.12;fill:none;stroke-linejoin:round}
10
10
  .kyogijo .ky-bud-line{stroke:var(--ky-line);fill:none}
11
11
  .kyogijo .ky-bud-ink{stroke:var(--ky-ink);fill:none}
12
12
  .kyogijo .ky-bud-floor{fill:var(--ky-bud-floor,var(--ky-boards))}
@@ -18,7 +18,7 @@ const CLASSES = `
18
18
  .kyogijo .ky-bud-desk{fill:var(--ky-ink);fill-opacity:.55}
19
19
  .kyogijo .ky-bud-seat{fill:var(--ky-ink);fill-opacity:.42}
20
20
  .kyogijo .ky-bud-shrine{fill:var(--ky-red)}
21
- .kyogijo .ky-bud-edge{stroke:#fff;stroke-opacity:.2;stroke-width:.03;fill:none}
21
+ .kyogijo .ky-bud-edge{stroke:var(--ky-ink);stroke-opacity:.04;stroke-width:.03;fill:none}
22
22
  .kyogijo .ky-bud-dais{fill:var(--ky-bud-floor,var(--ky-boards));stroke:var(--ky-ink);stroke-opacity:.5;stroke-width:.04}
23
23
  .kyogijo .ky-bud-banner{fill:var(--ky-ink);fill-opacity:.85}
24
24
  .kyogijo .ky-bud-clay{fill:var(--ky-dirt)}
@@ -54,6 +54,9 @@ export const KYOGIJO_BUDO_GALLERY_STYLE = `
54
54
  .kyogijo .ky-bud-gbed.ky-bud-gbed{fill:var(--ky-bud-gal,#7a5a3c)}
55
55
  .kyogijo .ky-bud-gblk.ky-bud-gblk{fill:var(--ky-bud-gpeople,#a89a86)}
56
56
  .kyogijo .ky-bud-gseat.ky-bud-gseat{stroke:var(--ky-bud-grail,#3a2c1f);stroke-opacity:.3}
57
+ .kyogijo .ky-bud-grow{stroke:var(--ky-bud-grail,#3a2c1f);stroke-opacity:.42}
58
+ .kyogijo .ky-bud-gback{stroke:var(--ky-bud-grail,#3a2c1f);stroke-opacity:.75}
59
+ .kyogijo .ky-bud-gaisle{stroke:var(--ky-bud-gaisle,#a98863)}
57
60
  .kyogijo .ky-bud-rail{stroke:var(--ky-bud-grail,#3a2c1f);stroke-width:1.6;stroke-opacity:.85}
58
61
  `;
59
62
  /** The rules of a drawing of a budō sport in a look: the shared rules and the look's colours. Nothing for another sport. */
@@ -10,6 +10,3 @@ export type { SplitRow, SplitsOptions, SplitsTotals, SwimmerSplits } from "./cha
10
10
  export { passNetwork } from "./charts.network.ts";
11
11
  export type { PassNetworkOptions, PassNetworkPass, PassNetworkTotals } from "./charts.network.ts";
12
12
  export type { Ball, Chart, ChartOptions, CurlingHeatOptions, CurlingHeatTotals, CurlingOptions, CurlingStone, CurlingStoneResult, CurlingTotals, GoalMouth, GoalMouthOptions, KeyItem, MlbSprayOptions, MlbTotals, NbaShot, NbaShotOptions, NbaTotals, NflPass, NflPassOptions, NflTotals, NhlShot, NhlShotOptions, NhlTotals, SoccerChart, SoccerShot, SoccerShotOptions, SoccerTotals, ZoneTotal } from "./charts.types.ts";
13
- export { ROAD_SIZES, roadToFinal } from "./road.ts";
14
- export { KYOGIJO_ROAD_STYLE } from "./road.style.ts";
15
- export type { RoadChart, RoadDecided, RoadFinal, RoadFinalist, RoadGroupMatch, RoadMatch, RoadOptions, RoadRecord, RoadScore, RoadSize, RoadSizeInfo, RoadTotals } from "./road.types.ts";
@@ -1,4 +1,4 @@
1
- // The ready-made charts: shot charts, shot maps, spray charts and pass charts built on the data layers, and the road to a final.
1
+ // The ready-made charts: shot charts, shot maps, spray charts and pass charts built on the data layers.
2
2
  export { nbaShotChart, nflPassChart, nhlShotMap } from "./charts.js";
3
3
  export { mlbSprayChart } from "./charts.baseball.js";
4
4
  export { soccerGoalMouth, soccerShotMap } from "./charts.soccer.js";
@@ -7,5 +7,3 @@ export { curlingHeat } from "./charts.curling.heat.js";
7
7
  export { CURLING_HIT_TYPES, curlingEndStats, curlingFreeGuards, curlingGuardScores, curlingHammers, curlingInPlay, curlingIsHit, curlingShotStats } from "./curling.stats.js";
8
8
  export { splitsChart } from "./charts.pool.js";
9
9
  export { passNetwork } from "./charts.network.js";
10
- export { ROAD_SIZES, roadToFinal } from "./road.js";
11
- export { KYOGIJO_ROAD_STYLE } from "./road.style.js";
package/dist/crowd.d.ts CHANGED
@@ -42,6 +42,8 @@ export interface StandRing {
42
42
  rake: number;
43
43
  /** The share of its seats that are taken, relative to the stands' (the upper tier is emptier). */
44
44
  fill: number;
45
+ /** A hall's gallery: the rows of seats the band holds. */
46
+ rows?: number;
45
47
  /** The seats the band holds, at a seat's 2 by 2.3 feet. */
46
48
  seats: number;
47
49
  /** The aisles and vomitories across the band, each from a point on its inner edge to its outer: across the sides, fanning round the corners. */
@@ -154,6 +156,8 @@ export interface LayoutInput {
154
156
  offset: number;
155
157
  depth: number;
156
158
  height: [number, number];
159
+ rows?: number;
160
+ aisles?: readonly number[];
157
161
  };
158
162
  /** The ballpark's fence as points on the field, for a ballpark (see `parkLayout`). */
159
163
  fence?: readonly Pt[];
@@ -1,4 +1,9 @@
1
1
  import { type Crowd, type CrowdStands } from "./crowd.ts";
2
+ /** The tiles a crowd needs: the home tile, the home tile of the upper tiers (emptier) and the visitors', and the display tiles. */
3
+ export declare function patterns(id: string, crowd: Crowd, density: number, tifo: {
4
+ home: boolean;
5
+ away: boolean;
6
+ } | undefined, neutral?: boolean, rows?: boolean): string;
2
7
  /** How the crowd is drawn: the prefix of ids, the options read, the share of seats taken, the detail, and the class of the ground the stands stand on. */
3
8
  export interface DrawInput {
4
9
  id: string;
@@ -49,8 +49,10 @@ function tile(seed, density, share, team, neutral = false) {
49
49
  return el("g", { fill: "none", "stroke-linecap": "round", "stroke-width": 2.3 }, out + headPaths);
50
50
  }
51
51
  /** The tiles a crowd needs: the home tile, the home tile of the upper tiers (emptier) and the visitors', and the display tiles. */
52
- function patterns(id, crowd, density, tifo, neutral = false) {
53
- const make = (suffix, body, size = TILE) => el("pattern", { id: `${id}-cr-${suffix}`, width: size, height: size, patternUnits: "userSpaceOnUse" }, body);
52
+ export function patterns(id, crowd, density, tifo, neutral = false, rows = false) {
53
+ // A 3D view's tiles carry the lines of the rows too (a row every third of a tile, along the tile's x), so that a raked plane is textured and not drawn seat by seat.
54
+ const rowLines = rows ? el("path", { class: "ky-cr-row", d: `M0 .3H${TILE}M0 ${TILE / 3 + 0.3}H${TILE}M0 ${(2 * TILE) / 3 + 0.3}H${TILE}`, fill: "none" }) : "";
55
+ const make = (suffix, body, size = TILE) => el("pattern", { id: `${id}-cr-${suffix}`, width: size, height: size, patternUnits: "userSpaceOnUse" }, size === TILE ? body + rowLines : body);
54
56
  let out = make("h", tile(crowd.seed * 7 + 1, density, neutral ? 0 : 0.88, "ky-cr-h", neutral)) + make("a", tile(crowd.seed * 7 + 3, Math.min(1, density * 1.04), 0.94, "ky-cr-a"));
55
57
  if (tifo !== undefined) {
56
58
  const stripes = (cls) => el("rect", { class: cls, width: 3, height: 6 }) + el("rect", { class: "ky-cr-tw", x: 3, width: 3, height: 6 });
@@ -105,6 +107,30 @@ function rows(ring) {
105
107
  }
106
108
  return d.length === 0 ? "" : el("path", { class: "ky-cr-row", d: d.join("") });
107
109
  }
110
+ /**
111
+ * The seats of a hall's gallery seen from above: its band stepped into rows (a gallery's rows are about 0.8 m, 2.6 feet, deep), each row a dashed line for the cushions with a thinner, darker one behind
112
+ * it for the backs, and the aisles across the band as lighter stripes between the blocks of seats. Plain seats and empty: the people, when asked for, are laid over.
113
+ */
114
+ function galleryRows(ring) {
115
+ const h = ring.hole;
116
+ const depth = ring.to - ring.from;
117
+ const rows = ring.rows ?? Math.max(1, Math.round(depth / 2.62));
118
+ if (h === null || h.kind !== "rrect")
119
+ return "";
120
+ const step = depth / rows;
121
+ const line = (k) => outlinePath({ kind: "rrect", x0: h.x0 - k, y0: h.y0 - k, x1: h.x1 + k, y1: h.y1 + k, r: h.r + k });
122
+ const cushions = [];
123
+ const backs = [];
124
+ for (let i = 0; i < rows; i += 1) {
125
+ const k = (i + 0.5) * step;
126
+ cushions.push(line(k - step * 0.12));
127
+ backs.push(line(k + step * 0.32));
128
+ }
129
+ const gaps = ring.aisles.map(([[x0, y0], [x1, y1]]) => `M${n1(x0)} ${n1(y0)}L${n1(x1)} ${n1(y1)}`).join("");
130
+ return (el("path", { class: "ky-bud-grow", d: cushions.join(""), fill: "none", "stroke-width": num(step * 0.5), "stroke-dasharray": "1.5 .5" }) +
131
+ el("path", { class: "ky-bud-gback", d: backs.join(""), fill: "none", "stroke-width": num(step * 0.16), "stroke-dasharray": "1.5 .5" }) +
132
+ (gaps === "" ? "" : el("path", { class: "ky-bud-gaisle", d: gaps, fill: "none", "stroke-width": 3.4 })));
133
+ }
108
134
  const poly = (points) => `M${points.map(([x, y]) => `${Math.round(x)} ${Math.round(y)}`).join("L")}Z`;
109
135
  /** The stands and the crowd as markup and the defs it uses. The markup is laid over the ground and under the surface. */
110
136
  export function drawCrowd(stands, o) {
@@ -134,7 +160,10 @@ export function drawCrowd(stands, o) {
134
160
  // The upper tiers are a little emptier: their paint is the same tile with the empty seats' bed showing through a veil.
135
161
  // The bed of seats under the tile, so that no seam of the pattern shows light.
136
162
  const bed = stands.gallery === true ? "ky-cr-bed ky-bud-gbed" : "ky-cr-bed";
137
- if (fine)
163
+ // A hall's gallery, seen from above, is neat rows of seats along its band (a few people are not drawn: it is subtle).
164
+ if (stands.gallery === true && fine)
165
+ body += el("path", { class: bed, d: path(ring) }) + galleryRows(ring) + (density > 0 ? el("path", { class: "ky-cr-people", d: path(ring), fill: `url(#${id}-cr-h)` }) : "");
166
+ else if (fine)
138
167
  body += el("path", { class: bed, d: path(ring) }) + el("path", { class: "ky-cr-people", d: path(ring), fill: `url(#${id}-cr-h)` });
139
168
  else
140
169
  body += el("path", { class: bed, d: path(ring) }) + el("path", { class: stands.gallery === true ? "ky-cr-blk ky-bud-gblk" : "ky-cr-blk", d: path(ring), "fill-opacity": num(0.28 + 0.6 * density * ring.fill) });
@@ -145,7 +174,7 @@ export function drawCrowd(stands, o) {
145
174
  body += sector(s, id, fine, density);
146
175
  if (fine)
147
176
  for (const ring of stands.rings)
148
- body += rows(ring) + (stands.gallery === true ? "" : aisles(ring));
177
+ body += stands.gallery === true ? "" : rows(ring) + aisles(ring);
149
178
  // Depth: the front wall of a tier throws a shade on its first rows (and the ground in front of it), the roof's edge on the last rows of the upper tier.
150
179
  // A gallery's railing: a line along its front edge, over the seats.
151
180
  if (stands.gallery === true)
@@ -166,7 +195,7 @@ export function drawCrowd(stands, o) {
166
195
  const [x0, y0, x1, y1] = g.box;
167
196
  body += el("rect", { class: `ky-cr-${g.kind}`, x: n1(x0), y: n1(y0), width: n1(x1 - x0), height: n1(y1 - y0), rx: g.kind === "bench" ? 1 : undefined });
168
197
  }
169
- return { defs: (fine ? patterns(id, crowd, density, tifo.home || tifo.away ? tifo : undefined, stands.gallery === true) : ""), markup: el("g", { class: "ky-crowd" }, body), over };
198
+ return { defs: (fine && (stands.gallery !== true || density > 0) ? patterns(id, crowd, density, tifo.home || tifo.away ? tifo : undefined, stands.gallery === true) : ""), markup: el("g", { class: "ky-crowd" }, body), over };
170
199
  }
171
200
  /** A block of visitors or a card display, a polygon over the band it is in. */
172
201
  function sector(s, id, fine, density) {
package/dist/crowd.js CHANGED
@@ -284,6 +284,10 @@ function aislesOf(region, hole) {
284
284
  }
285
285
  return [];
286
286
  }
287
+ /** The aisles of a hall's gallery: on each straight side, one across the band at each of the `shares` (0 to 1) of the side's length, from the first side's left end. */
288
+ function galleryAisles(region, hole, shares) {
289
+ return [0, 2, 4, 6].flatMap((side) => shares.map((f) => [rrectAt(hole, side + f).p, rrectAt(region, side + f).p]));
290
+ }
287
291
  /** The polygon of a band between two aisle parameters: along the outer edge, then back along the inner. */
288
292
  function sectionBetween(region, hole, u0, u1) {
289
293
  const us = [u0];
@@ -341,7 +345,8 @@ export function layoutOf(input) {
341
345
  rake: Math.round((Math.atan2(rise, Math.max(1, t.to - t.from)) * 180) / Math.PI * 10) / 10,
342
346
  fill: t.fill,
343
347
  seats: seatsIn(region, hole),
344
- aisles: aislesOf(region, hole),
348
+ aisles: gal?.aisles !== undefined && region.kind === "rrect" && hole.kind === "rrect" ? galleryAisles(region, hole, gal.aisles) : aislesOf(region, hole),
349
+ ...(gal?.rows === undefined ? {} : { rows: gal.rows }),
345
350
  });
346
351
  reachOf.left = Math.max(reachOf.left, off.left + t.to);
347
352
  reachOf.right = Math.max(reachOf.right, off.right + t.to);
package/dist/draw.js CHANGED
@@ -424,7 +424,7 @@ function standsFor(o, surface, crowd) {
424
424
  }
425
425
  // A dojo's crowd sits in its gallery, from the room's balcony data (in feet here); the arenas (the Budokan, the Kokugikan) keep their bowls.
426
426
  const hall = sport === "dojo" ? dojoGallery(o) : undefined;
427
- const gallery = hall === undefined ? undefined : { offset: hall.offset / 0.3048, depth: hall.depth / 0.3048, height: [hall.height[0] / 0.3048, hall.height[1] / 0.3048] };
427
+ const gallery = hall === undefined ? undefined : { offset: hall.offset / 0.3048, depth: hall.depth / 0.3048, height: [hall.height[0] / 0.3048, hall.height[1] / 0.3048], rows: hall.rows, aisles: hall.aisles };
428
428
  return crowdLayout({ sport, box: surface.whole ?? surface.box ?? { x: 0, y: 0, w: 100, h: 100 }, crowd, awaySide, ground: o.ground, arena: o.budo?.hall === "budokan", ...(gallery === undefined ? {} : { gallery }) });
429
429
  }
430
430
  /** True when the drawing is asked to be small: `detail: "simple"` or a `width` under 480 pixels. */
@@ -451,8 +451,8 @@ function buildCrowded(o, portrait, id, score, scored, extra, crowd) {
451
451
  const fine = crowd.detail === "fine" || (crowd.detail === "auto" && !smallDrawing(o));
452
452
  const first = /^<[^>]*\/>/.exec(surface.base)?.[0] ?? "";
453
453
  const bg = /class="([^"]*)"/.exec(first)?.[1] ?? "ky-apron";
454
- // A gallery is sparsely taken unless the options say how full.
455
- const taken = stands.gallery === true && crowd.density === undefined && crowd.attendance === undefined ? 0.4 : densityOf(crowd, seats, ground);
454
+ // A gallery is empty unless the options say how full: its seats are the detail.
455
+ const taken = stands.gallery === true && crowd.density === undefined && crowd.attendance === undefined ? 0 : densityOf(crowd, seats, ground);
456
456
  const drawn = drawCrowd(stands, { id, crowd, density: taken, fine, ground: bg });
457
457
  surface.base = first + drawn.markup + surface.base.slice(first.length);
458
458
  surface.defs += drawn.defs;
@@ -476,6 +476,9 @@ function colourStyle(o, teams, features) {
476
476
  if (c !== undefined)
477
477
  for (const [key, property] of [["mat", "--ky-surface"], ["alt", "--ky-surface-alt"], ["border", "--ky-red"], ["heri", "--ky-bud-heri"], ["floor", "--ky-bud-floor"], ["wall", "--ky-boards"]])
478
478
  set(property, c[key]);
479
+ // A heri the dojo's colours name is drawn as a cloth edge; the stylesheet's own is a faint seam.
480
+ if (safeColour(c?.heri) !== undefined)
481
+ props.push("--ky-bud-hop:.82");
479
482
  }
480
483
  if (o.court !== undefined)
481
484
  for (const [key, property] of Object.entries(COURT_PROPERTIES))
package/dist/element.d.ts CHANGED
@@ -6,8 +6,8 @@ import type { ScoreOptions, SurfaceOptions } from "./types.ts";
6
6
  /** The tag of the element. */
7
7
  export declare const KYOGIJO_TAG = "kyogijo-field";
8
8
  /** The attributes the element reads. */
9
- export declare const KYOGIJO_ATTRIBUTES: readonly ["sport", "league", "size", "theme", "language", "orientation", "park", "ground", "arena", "year", "detail", "margin", "stripes", "exact-lines", "uppercase", "pitch", "pitch-type", "half", "range", "range-end", "goals", "stripe-pattern", "corner-flags", "look", "line-width", "line-dash", "line-opacity", "landmarks", "walls", "quirks", "home", "away", "home-abbr", "away-abbr", "home-score", "away-score", "home-penalties", "away-penalties", "score", "home-logo", "away-logo", "home-color", "away-color", "status", "competition", "date", "label", "winner", "home-side", "pageant", "description", "options", "loop", "speed", "autoplay", "controls", "trail", "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch"];
10
- /** The settings of a 3D view an element's attributes make: `turn-view` names the viewpoint it opens on, `turn-controls="false"` and `turn-uprights="false"` leave the buttons and what stands on the surface out, `turn-score="floor"` leaves the score panel on the floor, `turn-touch="turn"` keeps every touch for the view. */
9
+ export declare const KYOGIJO_ATTRIBUTES: readonly ["sport", "league", "size", "theme", "language", "orientation", "park", "ground", "arena", "year", "detail", "margin", "stripes", "exact-lines", "uppercase", "pitch", "pitch-type", "half", "range", "range-end", "goals", "stripe-pattern", "corner-flags", "look", "line-width", "line-dash", "line-opacity", "landmarks", "walls", "quirks", "home", "away", "home-abbr", "away-abbr", "home-score", "away-score", "home-penalties", "away-penalties", "score", "home-logo", "away-logo", "home-color", "away-color", "status", "competition", "date", "label", "winner", "home-side", "pageant", "description", "options", "loop", "speed", "autoplay", "controls", "trail", "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch", "turn-venue", "turn-roof", "turn-autoplay", "turn-speed"];
10
+ /** The settings of a 3D view an element's attributes make: `turn-view` names the viewpoint it opens on, `turn-controls="false"` and `turn-uprights="false"` leave the buttons and what stands on the surface out, `turn-score="floor"` leaves the score panel on the floor, `turn-touch="turn"` keeps every touch for the view, `turn-venue="false"` leaves out the stands and the room (they are raised for a drawing with a crowd and for a dojo), `turn-roof` puts a roof or canopy over the stands, `turn-autoplay` starts the auto-turn (`force` even for a reader who prefers reduced motion) and `turn-speed` is its degrees a second. */
11
11
  export declare function turnSettingsFromAttributes(read: (name: string) => string | null, base?: TurnSettings): TurnSettings;
12
12
  /** Whether an element's `turn` attribute asks for the 3D view: present, and not `false`. */
13
13
  export declare const turnWanted: (read: (name: string) => string | null) => boolean;
@@ -24,7 +24,7 @@ export declare function optionsFromAttributes(read: (name: string) => string | n
24
24
  declare const Base: typeof HTMLElement;
25
25
  export declare class KyogijoField extends Base {
26
26
  #private;
27
- static observedAttributes: readonly ["sport", "league", "size", "theme", "language", "orientation", "park", "ground", "arena", "year", "detail", "margin", "stripes", "exact-lines", "uppercase", "pitch", "pitch-type", "half", "range", "range-end", "goals", "stripe-pattern", "corner-flags", "look", "line-width", "line-dash", "line-opacity", "landmarks", "walls", "quirks", "home", "away", "home-abbr", "away-abbr", "home-score", "away-score", "home-penalties", "away-penalties", "score", "home-logo", "away-logo", "home-color", "away-color", "status", "competition", "date", "label", "winner", "home-side", "pageant", "description", "options", "loop", "speed", "autoplay", "controls", "trail", "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch"];
27
+ static observedAttributes: readonly ["sport", "league", "size", "theme", "language", "orientation", "park", "ground", "arena", "year", "detail", "margin", "stripes", "exact-lines", "uppercase", "pitch", "pitch-type", "half", "range", "range-end", "goals", "stripe-pattern", "corner-flags", "look", "line-width", "line-dash", "line-opacity", "landmarks", "walls", "quirks", "home", "away", "home-abbr", "away-abbr", "home-score", "away-score", "home-penalties", "away-penalties", "score", "home-logo", "away-logo", "home-color", "away-color", "status", "competition", "date", "label", "winner", "home-side", "pageant", "description", "options", "loop", "speed", "autoplay", "controls", "trail", "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch", "turn-venue", "turn-roof", "turn-autoplay", "turn-speed"];
28
28
  /** The options given as a property: an object, laid under the attributes. */
29
29
  get options(): Partial<ScoreOptions> | undefined;
30
30
  set options(value: Partial<ScoreOptions> | undefined);
package/dist/element.js CHANGED
@@ -25,11 +25,11 @@ export const KYOGIJO_ATTRIBUTES = [
25
25
  "home", "away", "home-abbr", "away-abbr", "home-score", "away-score", "home-penalties", "away-penalties", "score", "home-logo", "away-logo", "home-color", "away-color",
26
26
  "status", "competition", "date", "label", "winner", "home-side", "pageant", "description", "options",
27
27
  "loop", "speed", "autoplay", "controls", "trail",
28
- "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch",
28
+ "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch", "turn-venue", "turn-roof", "turn-autoplay", "turn-speed",
29
29
  ];
30
30
  /** The attributes that are about a replay and not about the drawing. */
31
31
  const REPLAY_ATTRIBUTES = ["loop", "speed", "autoplay", "controls", "trail"];
32
- /** The settings of a 3D view an element's attributes make: `turn-view` names the viewpoint it opens on, `turn-controls="false"` and `turn-uprights="false"` leave the buttons and what stands on the surface out, `turn-score="floor"` leaves the score panel on the floor, `turn-touch="turn"` keeps every touch for the view. */
32
+ /** The settings of a 3D view an element's attributes make: `turn-view` names the viewpoint it opens on, `turn-controls="false"` and `turn-uprights="false"` leave the buttons and what stands on the surface out, `turn-score="floor"` leaves the score panel on the floor, `turn-touch="turn"` keeps every touch for the view, `turn-venue="false"` leaves out the stands and the room (they are raised for a drawing with a crowd and for a dojo), `turn-roof` puts a roof or canopy over the stands, `turn-autoplay` starts the auto-turn (`force` even for a reader who prefers reduced motion) and `turn-speed` is its degrees a second. */
33
33
  export function turnSettingsFromAttributes(read, base = {}) {
34
34
  const out = { ...base };
35
35
  const view = read("turn-view");
@@ -43,6 +43,20 @@ export function turnSettingsFromAttributes(read, base = {}) {
43
43
  out.score = "floor";
44
44
  if (read("turn-touch") === "turn")
45
45
  out.touch = "turn";
46
+ // The venue: off with `false`; `turn-roof` (present, and not `false`) asks for the roof.
47
+ const roof = read("turn-roof") !== null && read("turn-roof") !== "false";
48
+ if (read("turn-venue") === "false")
49
+ out.venue = false;
50
+ else if (roof)
51
+ out.venue = { ...(typeof out.venue === "object" ? out.venue : {}), roof: true };
52
+ const auto = read("turn-autoplay");
53
+ if (auto === "force")
54
+ out.autoPlay = "force";
55
+ else if (auto !== null && auto !== "false")
56
+ out.autoPlay = true;
57
+ const speed = Number(read("turn-speed"));
58
+ if (read("turn-speed") !== null && Number.isFinite(speed))
59
+ out.speed = speed;
46
60
  return out;
47
61
  }
48
62
  /** Whether an element's `turn` attribute asks for the 3D view: present, and not `false`. */
package/dist/halls.js CHANGED
@@ -1,11 +1,12 @@
1
1
  /** The Kodokan's main dojo as drawn: a square floor of 420 jō's area, four contest areas, the seat of honour, the banner, the wood and the gallery, in the two looks the owner's photographs show. The years of both are unknown. */
2
2
  const KODOKAN_MAIN = {
3
3
  mats: { width: 26.4, length: 26.4 },
4
- surround: 1,
4
+ surround: 4,
5
5
  areas: { across: 2, down: 2 },
6
6
  dais: true,
7
7
  banner: true,
8
- balcony: true,
8
+ // Per the owner, with his photographs: a gallery of 3 rows of seats on each side, the blocks of a row 5, 8, 8, 8 and 5 seats (34 to a row, 102 to a side) with an aisle between them.
9
+ balcony: { rows: 3, sections: [5, 8, 8, 8, 5] },
9
10
  kamiza: "north",
10
11
  eras: [
11
12
  { name: "earlier", nameJa: "以前", from: null, to: null, look: "green-orange", outline: true },
@@ -31,7 +32,7 @@ export const HALLS = Object.freeze([
31
32
  fromWhat: { en: "founded by Kanō Jigorō (the International Judo Center, the building the dojos are in, was completed in 1984)", ja: "嘉納治五郎が創設(道場のある講道館国際柔道センターは1984年に完成)" },
32
33
  leagues: [],
33
34
  surfaces: [
34
- { id: "main-dojo", name: { en: "Main dojo, 7th floor", ja: "大道場(7階)" }, sport: "dojo", options: { dojo: KODOKAN_MAIN }, basis: "count", mats: 420, note: { en: "420 jō on the 7th floor, four contest areas at once, with a gallery on the 8th that seats about 900. Per the owner's photographs: four contest areas in two rows, pale yellow mats on green safety bands and a green border, on a wood surround, a raised seat of honour with a dark banner at the front wall, a gallery on all four sides. The shape of the floor is not documented: a square of 26.4 m, the area of 420 jō, is drawn so that four areas of 8 m fit.", ja: "7階に420畳、同時に4面の試合ができ、8階に約900人が座れる見学席があります。提供された写真では、試合場が2列に4面並び、淡い黄色の畳の外側を緑の安全地帯と緑の縁取りが囲んでいます。周囲は板張りで、正面の上座には一段高い台と濃い色の垂れ幕があり、四方に見学席があります。床の形は資料にないので、420畳の広さにあたる26.4m四方の床に、8m四方の試合場4面が収まるように描いています。" } },
35
+ { id: "main-dojo", name: { en: "Main dojo, 7th floor", ja: "大道場(7階)" }, sport: "dojo", options: { dojo: KODOKAN_MAIN }, basis: "count", mats: 420, note: { en: "420 jō on the 7th floor, four contest areas at once, with a gallery on the 8th that seats about 900. Per the owner's photographs: four contest areas in two rows, pale yellow mats on green safety bands and a green border, on a wide wood floor all round (about 4 m, ours), a raised seat of honour with a dark banner at the front wall, a gallery on all four sides. The shape of the floor is not documented: a square of 26.4 m, the area of 420 jō, is drawn so that four areas of 8 m fit. Per the owner and his photographs: the gallery has 3 rows of seats on each side, in blocks of 5, 8, 8, 8 and 5 seats (34 a row, 102 a side) divided by aisles, behind a white rail; the lower wall is honey wood panelling and the upper wall plain off-white.", ja: "7階に420畳、同時に4面の試合ができ、8階に約900人が座れる見学席があります。提供された写真では、試合場が2列に4面並び、淡い黄色の畳の外側を緑の安全地帯と緑の縁取りが囲んでいます。周囲は板張りで、正面の上座には一段高い台と濃い色の垂れ幕があり、四方に見学席があります。床の形は資料にないので、420畳の広さにあたる26.4m四方の床に、8m四方の試合場4面が収まるように描いています。提供者の数え方と写真によると、見学席は各辺3列で、1列は5席・8席・8席・8席・5席のブロック(1列34席、1辺102席)に通路で区切られ、白い手すりの奥に並びます。下の壁は蜂蜜色の板張り、上の壁は無地のオフホワイトです。" } },
35
36
  { id: "main-dojo-earlier", name: { en: "Main dojo, 7th floor (earlier colours)", ja: "大道場(7階・以前の色)" }, sport: "dojo", options: { dojo: { ...KODOKAN_MAIN, era: "earlier" } }, basis: "count", mats: 420, note: { en: "The same room in the colours of the owner's older photograph: all green mats, the four contest areas outlined by red-orange border mats. No source gives the years of either look, so the eras carry none.", ja: "提供された古い写真の色で描いた同じ部屋です。畳はすべて緑で、4面の試合場それぞれを赤橙色の縁の畳が囲んでいます。どちらの色だった時期も資料にないので、年は記していません。" } },
36
37
  { id: "school-dojo", name: { en: "Judo School dojo, 6th floor", ja: "学校道場(6階)" }, sport: "dojo", options: { dojo: { jo: 240, look: "green-beige" } }, basis: "count", mats: 240, note: { en: "240 jō on the 6th floor; the shape is plausible.", ja: "6階に240畳。形は推定です。" } },
37
38
  { id: "international-dojo", name: { en: "International dojo, 6th floor", ja: "国際部道場(6階)" }, sport: "dojo", options: { dojo: { jo: 192, look: "green-beige" } }, basis: "count", mats: 192, note: { en: "192 jō on the 6th floor; the shape is plausible.", ja: "6階に192畳。形は推定です。" } },
package/dist/layout.js CHANGED
@@ -8,7 +8,6 @@ import { drawKyogijo } from "./draw.js";
8
8
  import { el, esc, hash, num } from "./format.js";
9
9
  import { KYOGIJO_GRID_STYLE } from "./layout.style.js";
10
10
  import { KYOGIJO_MARK_STYLE } from "./mark.js";
11
- import { KYOGIJO_ROAD_STYLE } from "./road.style.js";
12
11
  import { isPolygon, shapePath } from "./layers.marks.js";
13
12
  import { elements, parseSvg, textOf, writeSvg } from "./scene.xml.js";
14
13
  import { KYOGIJO_STRINGS, fill } from "./strings.js";
@@ -134,7 +133,7 @@ function renamed(root, prefix) {
134
133
  return { styles };
135
134
  }
136
135
  /** The rules the package writes into a drawing, in the order it writes them: peeled off the front of a style so that each is said once however many drawings carry it. */
137
- const KNOWN = [KYOGIJO_BASE_STYLE, KYOGIJO_CURLING_STYLE, KYOGIJO_HEAT_STYLE, KYOGIJO_TEAM_STYLE, KYOGIJO_NOTE_STYLE, KYOGIJO_BACKGROUND_STYLE, KYOGIJO_MARK_STYLE, KYOGIJO_ROAD_STYLE];
136
+ const KNOWN = [KYOGIJO_BASE_STYLE, KYOGIJO_CURLING_STYLE, KYOGIJO_HEAT_STYLE, KYOGIJO_TEAM_STYLE, KYOGIJO_NOTE_STYLE, KYOGIJO_BACKGROUND_STYLE, KYOGIJO_MARK_STYLE];
138
137
  /** A drawing's stylesheet as the pieces it is made of. */
139
138
  function pieces(css) {
140
139
  const out = [];
package/dist/mark.d.ts CHANGED
@@ -28,7 +28,7 @@ export interface RoundMark {
28
28
  ring?: number;
29
29
  /** The mark's group is turned back by this many degrees: pass the turn of the group the mark is in to hold it upright there. The score does not use it: its mark turns with the zone's words. */
30
30
  upright?: number;
31
- /** A class added to the mark's group (the road chart's `ky-r-dim` for a team that did not advance). */
31
+ /** A class added to the mark's group. */
32
32
  className?: string;
33
33
  }
34
34
  /** The markup of one round mark. */