@johnmorrisdotca/kyogijo-stadiums 0.46.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.48.0] - 2026-10-30
11
+
12
+ ### Added
13
+
14
+ - **The Scotiabank Saddledome is its own drawing (SPX-646).** A round building under one roof that is a reverse hyperbolic paraboloid (`src/venue.saddle.ts`): the roof is a net of quads on the surface `z = z0 + k (a² − b²)` over the round plan (`saddleHeight`; 8, 10 or 16 cells to a side at `low`, `auto` and `high` detail, the plan mapped on a square grid so every cell is a fair quad), a beam 5 feet deep round its edge that overhangs the wall, leaning walls in `4 × cells` facets with a pier at every second one, the lines of the cladding and a few windows, and at each of the two high points a round stair tower and a square one. Every quad is shaded by its own normal, cut away from above and from the eye's side, and whole from the street, with the roof and the walls on or off. The roof's line runs 60 degrees from north (east-northeast to west-southwest), its low rim stands 78 feet over the ice and its high points 138, read from three photographs of the building taken from known places and from the outline's one bulge (`roof.saddle` in `scripts/venues.data.json`, with what each was read from; the photographs are looked at as evidence and not copied). The bowl is round (`bowl.round`) and its tiers rise 20 and 30 feet (`bowl.rises`) so that the low rim clears the top row (`SADDLE_CLEARANCE`, 8 feet). Side-by-side sheets against the photographs are in the release notes' hand-off, and the page [docs/venues.md](./docs/venues.md#the-scotiabank-saddledome) says what the drawing leaves out.
15
+ - **How each rink's direction is known, looked at again for all thirty-two (SPX-645).** `pnpm venues:rink <date>` reads every sports, indoor and building-part feature OpenStreetMap maps within 260 metres of each arena (`data-sources/venues/2026-10-30/`), `pnpm venues` tests them against the first read (an ice inside a building that the venue does not hold is a failure) and writes the table of docs/venues.md with what the English Wikipedia articles and the Commons categories gave (`scripts/venues.rink.json`). Nothing new was found: only TD Garden, Xfinity Mobile Arena and PPG Paints Arena have their ice mapped (TD Garden's parquet floor mapped inside lies along it), the Bridgestone Arena's article says its floor has a north end (a point of the compass, not a heading), and the other arenas stay inferred or not known.
16
+ - `RealVenueData.bowl`, `RealVenueData.roof.saddle`, `RealShell.saddle`, `Saddle`, `saddleHeight`, `saddleRoof`, `saddleWalls`, `saddleTowers`, `quadPlane`, `SADDLE_CELLS`, `EAVE`, `TOWERS`, `SADDLE_CLEARANCE`; the demo's venues page lists a saddle roof's measure under the roof.
17
+
18
+ ### Changed
19
+
20
+ - The saddle roof of the Saddledome is no longer a band of flat pieces over a straight wall that rose 30 feet at the two ends of the rink: `SADDLE_RISE` and `saddleAt` are gone.
21
+
22
+ ### Cost
23
+
24
+ - The package's script grows by 17.7 KB (5.7 KB gzipped) on the venues page and the district pages (the saddle builder 14.5 KB, 4.8 KB gzipped, the shell's hook and the data), and its data by 1.3 KB (0.5 KB gzipped); style by 1.1 KB. The venues page is 3,065 KB against a budget of 3,165, the districts 2,428 against 2,500. No dependency is added. The Saddledome in 3D with its roof and walls is 718 planes at `high` (against 546; 133 KB of drawing against 71), 413 at `auto` (322; 74 KB against 46) and 295 at `low` (187; 48 KB against 31), the other thirty-one venues as they were; every page stays inside its byte budget.
25
+
26
+ ## [0.47.0] - 2026-10-30
27
+
28
+ ### Added
29
+
30
+ - **The Olympic Stadium's shell is the building's: 38 ribs, glazed bays and a low white roof (SPX-811).** In place of a plain tall drum under a low lens, the shell is 38 concrete consoles (34 anchored to the ground, 4 fixed to the tower: the French article) round the building's outline, each a curved wing that stands out at its foot and sweeps in to a ring, a glazed bay between each pair (a podium and three bands of glass), white roof panels climbing from the glazing to the ring, and a membrane closing the ring in a low cone, its crown the 52 metres over the field the English article gives. The stands are fitted inside the shell's sweep (`RIBBED_STANDS`), so no row stands out through the roof; the ring's height, the ribs' flare and the bays' profile are ours, from Commons photographs (`src/venue.ribbed.ts`, `RealShell.dome.ribs`, `StadiumRoof` data `ribs` and `top`). Compared with Commons photographs from the plaza, across the river, from Mount Royal and from below the tower (sheets in the backups folder, `2026-10-30-olympic-shell`). Left as it is, honestly: the real ribs are bigger and more curved, the real membrane is pleated and the real tower has about forty cables where the drawing has five.
31
+ - **The Montreal Tower's foot is at the rim, it flares and its back curves.** The shaft is twelve horizontal sections between a steep front, from where the mapped axis meets the building's rim to the observatory, and a back that sweeps down from the top in a long curve and flattens along the ground behind the foot; a fan of five cables replaces the three; no plinth. Height, top and axis are the sources' as before.
32
+ - **How the Olympic Stadium's field lies, to the nearest 45 degrees (SPX-811).** Nothing maps the field and no article gives a bearing, but the English Wikipedia's list of Montreal's ballparks names a street at first base (Pierre-De Coubertin), left field (Sherbrooke), third base (Pie-IX) and right field (Viau), which bound the stadium on its north, south, west and east: home plate is in the north-west corner and centre field to the south-east, 135 degrees. The new basis is `streets` (`StadiumOsm.axis.basis`, `orientation` in the facts, `said.stadiumStreets` in English and Japanese); the drawing says it is an inference and draws the arrow to north. The seats still fit (49,756 of 49,757).
33
+ - **Speed checks per device class (SPX-774).** Three Playwright projects, each a job of its own on the public repository's free CI (`device-desk`: Chromium 1280 by 800, ratio 1, held at `high`; `device-mid-phone`: WebKit, 390 by 844, ratio 3, `medium`; `device-slow-android`: Chromium 412 by 915, ratio 2.6, CPU slowed 6 times, `low`) open every district preset (all twelve, the New York and Montreal ones among them) and the venues page's 3D view and hold each to budgets (`e2e/budgets.device.mjs`, `e2e/device.budgets.json`, `e2e/device.lib.mjs`, docs/quality.md): exact counts that no runner changes (layers, those drawn, elements, the memory the layers take at the device's ratio, the biggest layer; budget = measured plus 15 percent) and a ratio of a step of the turn to the end of its frame over a reference scene measured beside it with the same CPU slowing (budget = twice what it measured, at least one more). The tier is pinned in the address, because `auto` steps down on a slow runner and a step changes what is counted. A test checks that every preset of the page has a budget. About 25 to 45 seconds each on a laptop, one to two minutes with the build on the runner; the three run beside the six demo jobs and add no time to the longest of them. `DEVICE_MEASURE=file.jsonl` writes what each page measured, to make the budgets again.
34
+
35
+ ### Changed
36
+
37
+ - The Olympic Stadium's district view is 1,096 planes at `high` (775 at `medium`, 424 at `low`), down from 1,111 (870, 519), and 146 more elements (4,503 against 4,357). At the lower tiers the ribs' outer faces and the ring's lip are left out as small parts, and at the coarsest cut there are 19 ribs.
38
+ - The package's `dist/` grows by 17.5 KB (5.4 KB gzipped) of script and data: the new shell builder 12.8 KB, the tower and the shell's hook 2 KB, the stadiums' facts 0.4 KB, the rest style and words; no dependency is added.
39
+ - `scripts/readme-pictures.mjs`: the stadiums page's Olympic picture is taken from the end-on viewpoint, so the tower is seen in profile.
40
+
10
41
  ## [0.46.0] - 2026-10-30
11
42
 
12
43
  ### Added
package/NOTICE.md CHANGED
@@ -423,6 +423,8 @@ The Japanese Wikipedia articles read for the names, with their revisions, are li
423
423
 
424
424
  The thirty-two arenas of the NHL, read on 2026-10-20 (`pnpm venues:fetch <date> <division>`; the files are in `data-sources/venues/2026-10-20/`: `osm-<arena>.json` as Overpass returned them, and `wikidata-<division>.json` and `wikipedia-<division>.json`). The outline of a building is OpenStreetMap's, ODbL 1.0, (c) OpenStreetMap contributors, and lives in `src/venues.osm.data.ts` alone. The seats, the tiers and the roof are in `src/venues.data.ts`, with what each cites. No seat map of a team, a ticket seller or a venue is read or copied, and no mark of a team or a league is drawn.
425
425
 
426
+ How each rink lies was looked at again on 2026-10-30 (`pnpm venues:rink 2026-10-30`; `data-sources/venues/2026-10-30/osm-evidence-<arena>.json`, as Overpass returned them: ODbL 1.0, (c) OpenStreetMap contributors, and used only to test whether an ice or a court is mapped inside a building; nothing of them is in a drawing). The English Wikipedia articles and the file descriptions of Wikimedia Commons were read for a stated direction, and nothing was copied from them. The Scotiabank Saddledome's roof lies as three photographs on Wikimedia Commons show it, taken from known places, each CC BY-SA 4.0 and looked at as evidence only (never copied, kept or shown by the package): "Scotiabank Saddledome, southeast view 20240819 1" and "Scotiabank Saddledome, southwest view 20240819 1" (by DXR) and "Scotiabank Saddledome from Calgary Tower, Calgary, Alberta, 2025-07-14" (by Chris Woodrich), as their file pages credit them. What each gave is a bearing and a height, in numbers of our own (`roof.saddle` in `scripts/venues.data.json`).
427
+
426
428
  <!-- venues-notice -->
427
429
  | Arena | Wikidata item | OpenStreetMap element | English article and revision |
428
430
  | --- | --- | --- | --- |
package/dist/crowd.d.ts CHANGED
@@ -139,11 +139,14 @@ export interface RealShell {
139
139
  top: number;
140
140
  parked: Pt[];
141
141
  };
142
- /** A `dome` roof: the outline of its rim in the drawing's frame, and the heights of the rim (`low`) and of the crown above the surface, in feet. It is held up by the air inside, so it is a smooth lens, with no struts. */
142
+ /** A `dome` roof: the outline of its rim in the drawing's frame, and the heights of the rim (`low`) and of the crown above the surface, in feet. It is held up by the air inside, so it is a smooth lens, with no struts. With `ribs` (the Olympic Stadium's) the shell is `count` concrete consoles with glazed panels between them and a roof of panels rising to a ring, and `low` is the ring's height. */
143
143
  dome?: {
144
144
  rim: Pt[];
145
145
  low: number;
146
146
  crown: number;
147
+ ribs?: {
148
+ count: number;
149
+ };
147
150
  };
148
151
  /** Draw the walls as brick (a ballpark's facade, T-Mobile Park's: from the brief; a plain drawing choice, a wall in a brick tone, no courses). */
149
152
  brick?: boolean;
@@ -176,6 +179,17 @@ export interface RealShell {
176
179
  };
177
180
  /** The height of the roof's lowest edge above the surface, in feet. */
178
181
  edge: number;
182
+ /**
183
+ * A `saddle` roof's measure, in the drawing's frame: the middle of its round plan and its radius, the unit direction of the line through its two high points, the height of those points (`high`) and of the low rim between them
184
+ * (`low`), in feet over the surface. The roof is the hyperbolic paraboloid through them (`venue.saddle.ts`), and the building round it is round.
185
+ */
186
+ saddle?: {
187
+ centre: Pt;
188
+ radius: number;
189
+ axis: Pt;
190
+ low: number;
191
+ high: number;
192
+ };
179
193
  /** Towers that stand apart from the bowl (Lumen Field's, at the open end), each a box on its outline in the drawing's frame, `height` feet high; `face` is the unit direction its scoreboard looks, towards the field. */
180
194
  towers?: {
181
195
  ring: Pt[];
package/dist/draw.js CHANGED
@@ -789,7 +789,7 @@ export function describe(o) {
789
789
  const venue = sport === "nhl" && league === undefined && realCrowd?.real === true ? venueById(o.arena) : undefined;
790
790
  const realLine = venue === undefined || arenaName === undefined ? "" : `${gap}${fill(words.said.real, { arena: arenaName, seats: String(venue.seats.hockey), tiers: String(venue.tiers.count) })}${gap}${words.said[venue.osm.axis.basis === "mapped" ? "realMapped" : venue.osm.axis.basis === "building" ? "realBuilding" : "realNone"]}`;
791
791
  const real = realStadiumOf(o, sport, realCrowd?.real === true);
792
- const stadiumKey = (basis, kind, config) => basis === "opening" ? "stadiumOpening" : basis === "pitch" ? (kind === "park" ? "stadiumPitchPark" : "stadiumPitch") : basis === "infield" ? "stadiumInfield" : basis === "building" ? (kind === "hall" ? (config === "nba" ? "stadiumCourtBuilding" : "stadiumRingBuilding") : "stadiumBuilding") : kind === "park" ? "stadiumNonePark" : "stadiumNone";
792
+ const stadiumKey = (basis, kind, config) => basis === "opening" ? "stadiumOpening" : basis === "pitch" ? (kind === "park" ? "stadiumPitchPark" : "stadiumPitch") : basis === "infield" ? "stadiumInfield" : basis === "streets" ? "stadiumStreets" : basis === "building" ? (kind === "hall" ? (config === "nba" ? "stadiumCourtBuilding" : "stadiumRingBuilding") : "stadiumBuilding") : kind === "park" ? "stadiumNonePark" : "stadiumNone";
793
793
  const stadiumLine = real === undefined ? "" : `${gap}${fill(real.config === "mlb" ? words.said.stadiumPark : stadiumKind(real.stadium) === "hall" ? words.said.stadiumHall : words.said.stadium, { stadium: stadiumNames(real.stadium)[language], seats: String(stadiumSeats(real.stadium, real.config)), tiers: String(real.stadium.tiers.count) })}${gap}${words.said[stadiumKey(real.stadium.osm.axis.basis, stadiumKind(real.stadium), real.config)]}`;
794
794
  const parkLine = (parkName === undefined ? `${groundLine}${halfLine}${arenaLine}${realLine}${stadiumLine}` : `${gap}${fill(words.said.park, { park: parkName })}${officially(parkOfficial)}${ballpark === undefined ? "" : ballparkSentences(ballpark).map((line) => `${gap}${line}`).join("")}${stadiumLine}`) + (special === undefined ? "" : `${gap}${special.text}`);
795
795
  // A drawing of a past season says which: the markings are those of that season.
@@ -62,6 +62,13 @@ export interface RealStadiumData {
62
62
  dirtInfield?: string;
63
63
  brickWalls?: string;
64
64
  };
65
+ /** Which way a ballpark's field lies where no map says but a published list does: the heading from home plate to centre field in degrees clockwise from north, `streets` (the streets the list names at the bases and the outfield), where it was read and what was read. */
66
+ orientation?: {
67
+ heading: number;
68
+ basis: "streets";
69
+ source: string;
70
+ read: string;
71
+ };
65
72
  configs: Partial<Record<StadiumConfigId, StadiumConfigData>>;
66
73
  /** The seats of the whole bowl where it holds more than a configuration's (Lumen Field's soccer seats are fewer: the upper sections are closed): the bowl is fitted to these and a configuration opens its seats from the lowest bowl up. */
67
74
  bowlSeats?: number;
@@ -124,9 +131,15 @@ export interface RealStadiumData {
124
131
  };
125
132
  /** A canopy's share of the seats it covers, as the article gives it. */
126
133
  cover?: number;
127
- /** A retractable roof's bottom and top above the field, in feet, as the article gives them (an air-supported roof's height is the building's, from OpenStreetMap). */
134
+ /** A retractable roof's bottom and top above the field, in feet, as the article gives them (an air-supported roof's height is the building's, from OpenStreetMap; a fixed roof's `top` is its crown's). */
128
135
  bottom?: number;
129
136
  top?: number;
137
+ /** A fixed roof on a frame of ribs (the Olympic Stadium's consoles): how many, how many of them are anchored to the ground, and where it is read. */
138
+ ribs?: {
139
+ count: number;
140
+ anchored: number;
141
+ source: string;
142
+ };
130
143
  };
131
144
  }
132
145
  /** What OpenStreetMap gave of a stadium: ODbL 1.0 data, "(c) OpenStreetMap contributors". */
@@ -177,12 +190,12 @@ export interface StadiumOsm {
177
190
  * How the field lies, worked out when the data was made: the heading of its long side (degrees clockwise from north, 0 to under 180; for a ballpark, the heading from home plate out to centre field, 0 to under 360), how that
178
191
  * is known (`opening`: from the roof's opening, which the article says is the field's size; `pitch`: from the pitches OpenStreetMap maps, and for a ballpark from the grass of the field it maps, fitted to the fence
179
192
  * the park publishes (`rms`, in metres, is how far the two lie apart); `infield`: from the mapped infield, with the outline choosing which way is out to centre field; `building`: from the long axis of the
180
- * building's main hall, an inference; `none`: no direction is known, and the heading is a stand-in (90 for a field with a long side, and for a ballpark the way that keeps its fence and its stands inside the building)),
193
+ * building's main hall, an inference; `streets`: from the streets a published list gives at the bases and the outfield of a ballpark, to the nearest 45 degrees, an inference; `none`: no direction is known, and the heading is a stand-in (90 for a field with a long side, and for a ballpark the way that keeps its fence and its stands inside the building)),
181
194
  * how much of a direction the building has (`strength`) and how much longer than wide the opening, the pitch or the hall is (`elongation`), and the middle of the field as `[longitude, latitude]` (home plate for a ballpark).
182
195
  */
183
196
  axis: {
184
197
  heading: number;
185
- basis: "opening" | "pitch" | "infield" | "building" | "none";
198
+ basis: "opening" | "pitch" | "infield" | "building" | "streets" | "none";
186
199
  strength: number;
187
200
  elongation: number;
188
201
  centre: [number, number];
@@ -118,7 +118,7 @@ export const STADIUM_DATA = [
118
118
  "configs": { "mlb": { "sport": "mlb", "seats": 49757, "source": "wikipedia", "read": "the infobox's capacity for baseball, 49,757 (1992 on, after 12,000 seats were taken out and home plate moved closer to the stands in the renovations of 1991); Wikidata's capacity (45,757) is kept beside it" } },
119
119
  "wikidataSeats": [45757],
120
120
  "tiers": { "count": 2, "basis": "plausible", "read": "no source read says how many seating levels there are: a lower and an upper bowl are drawn, the usual for a stadium of 50,000 seats (ours)" },
121
- "roof": { "kind": "fixed", "basis": "documented", "read": "the article: the Kevlar roof, which could retract, was removed in May 1998 and a $26 million non-retractable opaque blue roof was installed later that year; a doughnut-shaped permanent ring is the roof's permanent component" },
121
+ "roof": { "kind": "fixed", "basis": "documented", "read": "the article: the Kevlar roof, which could retract, was removed in May 1998 and a $26 million non-retractable opaque blue roof was installed later that year; a doughnut-shaped permanent ring is the roof's permanent component; the roof is only 52 m (170.6 ft) over the field of play (the English article) and the building 284 m by 245 m (the French article), the roof's profile, the glazed bays and the ribs' flare are ours, from photographs", "top": 171, "ribs": { "count": 38, "anchored": 34, "source": "https://fr.wikipedia.org/wiki/Stade_olympique_de_Montr%C3%A9al: the frame is 38 cantilevered consoles, 34 anchored to the ground and 4 fixed to the tower" } },
122
122
  "tower": { "height": 165, "unit": "m", "angle": 45, "basis": "documented", "read": "the article: the Montreal Tower, 165 m high, the tallest inclined structure in the world, inclined at 45 degrees and built into the north base of the stadium, from which the cables of the roof were hung; OpenStreetMap maps the building's outline and the same 165 m height, the observatory at its top (a viewpoint) and, as the part of the funicular line that rides on the tower (tagged above ground, its tunnel below), the line of the shaft from its foot to its top: that is 158 m across for 165 m of height, the article's 45 degrees. The slight curve of the shaft and its widths are ours." },
123
123
  },
124
124
  {
package/dist/stadiums.js CHANGED
@@ -339,9 +339,13 @@ const PARK_PLANS = new WeakMap();
339
339
  const PARK_COVER = 0.92;
340
340
  /** How far an open-air ballpark's walls stand over its top row, in feet (ours). */
341
341
  const OPEN_WALL_OVER = 6;
342
- /** How far a fixed roof's crown stands over its rim, in feet, and the width of a leaning tower's shaft at its base and at its top, in feet (ours: the sources give a roof's kind and a tower's height, not these). */
342
+ /** How far a fixed roof's crown stands over its rim, in feet (where no source gives its height), and the width of a leaning tower's shaft at its base and at its top, in feet (ours: the sources give a roof's kind and a tower's height, not these). */
343
343
  const FIXED_CROWN = 30;
344
344
  const TOWER_WIDTH = [240, 64];
345
+ /** How far in the outline the stands of a ribbed shell are fitted, as a share of the way to its middle: the shell's sweep clears the top row there (ours, from the shell's profile in `venue.ribbed.ts`). */
346
+ const RIBBED_STANDS = 0.87;
347
+ /** How far a ribbed shell's ring stands over the top row, in feet: the roof panels climb to it from the glazing, and the top row's back wall under them stays below the panel there (ours). */
348
+ const RIBBED_CLEARANCE = 36;
345
349
  const PARK_PARKED = 0.3;
346
350
  /**
347
351
  * The plan of a ballpark from what its stands are laid from (`input`: the park's fence, the backstop and the crowd): the usual stands with each band as deep as its seats need, the building's outline turned so that home
@@ -378,6 +382,10 @@ function park(stadium, input) {
378
382
  };
379
383
  const footprint = world.map(frameOf);
380
384
  const fb = boxOf(footprint);
385
+ // A ribbed shell (the Olympic Stadium's) rises over the bowl in a sweep from its outline in to a ring, so the stands must stand inside the shell: they are fitted inside the outline drawn in by `RIBBED_STANDS`.
386
+ const ribbed = stadium.roof.kind === "fixed" && stadium.roof.ribs !== undefined;
387
+ const mean = [footprint.reduce((t, p) => t + p[0], 0) / footprint.length, footprint.reduce((t, p) => t + p[1], 0) / footprint.length];
388
+ const fitted = ribbed ? footprint.map((p) => [mean[0] + (p[0] - mean[0]) * RIBBED_STANDS, mean[1] + (p[1] - mean[1]) * RIBBED_STANDS]) : footprint;
381
389
  // Each band is as deep as holds its seats: the usual depths scaled by what the seats of the band are to what the band holds, a few rounds over (a band's seats grow with its depth). The usual depths
382
390
  // take a seat to be 4.6 square feet of plan; a building too small for them has a steeper bowl, in which a seat takes less (the factors of `SEAT_AREAS`, as an arena's bowl), until the stands fit inside it.
383
391
  let depths = { lower: 60, upper: 56, bleachers: 54 };
@@ -394,7 +402,7 @@ function park(stadium, input) {
394
402
  depths[k] = Math.min(160, Math.max(20, next[k]));
395
403
  stands = parkLayout({ ...input, depths });
396
404
  }
397
- if (stands.rings.every((r) => [r.region, ...(r.more ?? [])].every((o) => o.kind !== "poly" || o.points.every((p) => inside(footprint, p)))))
405
+ if (stands.rings.every((r) => [r.region, ...(r.more ?? [])].every((o) => o.kind !== "poly" || o.points.every((p) => inside(fitted, p)))))
398
406
  break;
399
407
  }
400
408
  if (factor !== 1) {
@@ -423,11 +431,11 @@ function park(stadium, input) {
423
431
  else if (stadium.roof.kind === "fixed") {
424
432
  // A fixed roof over the whole bowl (the Olympic Stadium's): a low lens over the outline, from the walls (a little over the top row) to a crown a little higher over the middle (ours: the sources say the roof is fixed, not what shape it has),
425
433
  // and the tower that leans over it where OpenStreetMap maps one.
426
- const low = round(Math.max(top + STADIUM_CLEARANCE, (stadium.osm.height ?? 0) * FEET_PER_METRE));
434
+ const low = round(Math.max(top + (ribbed ? RIBBED_CLEARANCE : STADIUM_CLEARANCE), (stadium.osm.height ?? 0) * FEET_PER_METRE));
427
435
  const rim = footprint.map((p) => [c[0] + (p[0] - c[0]) * 0.98, c[1] + (p[1] - c[1]) * 0.98]);
428
436
  const mapped = stadium.osm.tower;
429
437
  const tower = mapped === undefined || stadium.tower === undefined ? undefined : { base: frameOf(metresFrom(origin, [mapped.base])[0]), top: frameOf(metresFrom(origin, [mapped.top])[0]), height: round(stadium.tower.height * FEET_PER_METRE), angle: stadium.tower.angle, width: [TOWER_WIDTH[0], TOWER_WIDTH[1]] };
430
- shell = { footprint, hall, ...(north === undefined ? {} : { north }), roof: "dome", edge: low, wall: low, dome: { rim, low, crown: low + FIXED_CROWN }, ...(tower === undefined ? {} : { tower }), ...ivy };
438
+ shell = { footprint, hall, ...(north === undefined ? {} : { north }), roof: "dome", edge: low, wall: low, dome: { rim, low, crown: stadium.roof.top === undefined ? low + FIXED_CROWN : Math.max(low + 6, stadium.roof.top), ...(stadium.roof.ribs === undefined ? {} : { ribs: { count: stadium.roof.ribs.count } }) }, ...(tower === undefined ? {} : { tower }), ...ivy };
431
439
  }
432
440
  else if (stadium.roof.kind === "open") {
433
441
  // No roof: the walls stand a little over the top row.
@@ -443,7 +443,7 @@ export const STADIUM_OSM = {
443
443
  "base": "2026-10-30T15:34:06Z",
444
444
  "licence": "ODbL-1.0",
445
445
  "attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
446
- "axis": { "heading": 345, "basis": "none", "strength": 1.21, "elongation": 1.28, "centre": [-73.551436, 45.557263] },
446
+ "axis": { "heading": 135, "basis": "streets", "strength": 1.21, "elongation": 1.28, "centre": [-73.552063, 45.558116] },
447
447
  "tower": { "source": "way/20213158", "height": 165, "base": [-73.552212, 45.55979], "top": [-73.551804, 45.558392], "ring": [
448
448
  [-73.551265, 45.559204],
449
449
  [-73.55152, 45.559114],
@@ -520,6 +520,7 @@ export interface KyogijoWords {
520
520
  stadiumPark: string;
521
521
  stadiumPitch: string;
522
522
  stadiumInfield: string;
523
+ stadiumStreets: string;
523
524
  stadiumHall: string;
524
525
  stadiumRingBuilding: string;
525
526
  stadiumCourtBuilding: string;
@@ -199,7 +199,7 @@ export const WORDS_EN = {
199
199
  },
200
200
  },
201
201
  scoreTitle: "{away} {awayScore}, {home} {homeScore}",
202
- said: { home: "home", away: "away", won: "{name} won.", tied: "The game is level.", park: "The outfield fence follows the figures published for {park}.", officially: "Its official name is {name}.", ground: "The pitch follows the size published for {ground}.", groundDefault: "No pitch size is published for {ground}, so the pitch has the international size of 105 by 68 metres.", arenaRink: "{arena}, hockey rink", arenaCourt: "{arena}, basketball court", arenaRinkAbout: "an ice hockey rink, {length} by {width} feet ({lengthM} by {widthM} metres) with rounded corners, with its red and blue lines, face-off circles, creases and goal nets", arenaCourtAbout: "a basketball court, {length} by {width} feet ({lengthM} by {widthM} metres), with its keys, three-point lines, free-throw circles and the centre circle", arenaSize: "The size is the one published for {arena}.", arenaUnknown: "The article says the rink of {arena} was shorter than the NHL's but gives no size, so the NHL's is drawn.", real: "The stands are a plausible bowl of {seats} seats in {tiers} tiers, fitted inside the outline of the building of {arena} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the arena's plan.", realBuilding: "The rink lies along the building's long axis, which is an inference, and the arrow points north.", realMapped: "The rink lies as OpenStreetMap maps the ice, and the arrow points north.", realNone: "The rink's orientation is not known, so it is drawn east-west with north at the top.", stadium: "The stands are a plausible bowl of {seats} seats in {tiers} bowls, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the stadium's plan.", stadiumOpening: "The field lies along the opening in the roof, which is the size of the field, and the arrow points north.", stadiumBuilding: "The field lies along the building's long axis, which is an inference, and the arrow points north.", stadiumNone: "The field's orientation is not known, so it is drawn east-west with north at the top.", stadiumHall: "The stands are a plausible bowl of {seats} seats in {tiers} levels, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the hall's plan.", stadiumRingBuilding: "The ring is set along the building's long axis, which is an inference, and the arrow points north.", stadiumCourtBuilding: "The court is set along the building's long axis, which is an inference, and the arrow points north.", stadiumPitchPark: "The way home plate faces is worked out from the grass of the field OpenStreetMap maps, fitted to the published fence, and the arrow points north.", stadiumNonePark: "The way home plate faces is not known, so the field is drawn the way that fits the building, and no arrow points north.", stadiumPark: "The stands are the usual stands of a ballpark, each band as deep as its seats need, for {seats} seats, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the park's plan.", stadiumPitch: "The field lies as OpenStreetMap maps the pitch, and the arrow points north.", stadiumInfield: "The way home plate faces is worked out from the infield OpenStreetMap maps and the building's outline, and the arrow points north.", district: "A plan of {count} real venues at their real positions and orientations, from the outlines OpenStreetMap maps; {mode}.", districtNeighbours: "it is fitted close round the buildings", districtCity: "it keeps the real distances, with a grid of kilometres and a scale", districtCredit: "Credit:", districtOutside: "People outside: {percent} percent of the seats of the venues, {people} people, stand on the sidewalks, plazas and grounds round them, and none on a road.", era: "The markings are those of the {season} season.", hammer: "{name} has the hammer.", ends: "By end, {name}: {list}.", total: "Total", wonPens: "{name} won {a}-{b} on penalties.", pens: "{a}-{b} pens", on: "Drawn as", sep: ", ", stop: ".", colon: ": ", gap: " " },
202
+ said: { home: "home", away: "away", won: "{name} won.", tied: "The game is level.", park: "The outfield fence follows the figures published for {park}.", officially: "Its official name is {name}.", ground: "The pitch follows the size published for {ground}.", groundDefault: "No pitch size is published for {ground}, so the pitch has the international size of 105 by 68 metres.", arenaRink: "{arena}, hockey rink", arenaCourt: "{arena}, basketball court", arenaRinkAbout: "an ice hockey rink, {length} by {width} feet ({lengthM} by {widthM} metres) with rounded corners, with its red and blue lines, face-off circles, creases and goal nets", arenaCourtAbout: "a basketball court, {length} by {width} feet ({lengthM} by {widthM} metres), with its keys, three-point lines, free-throw circles and the centre circle", arenaSize: "The size is the one published for {arena}.", arenaUnknown: "The article says the rink of {arena} was shorter than the NHL's but gives no size, so the NHL's is drawn.", real: "The stands are a plausible bowl of {seats} seats in {tiers} tiers, fitted inside the outline of the building of {arena} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the arena's plan.", realBuilding: "The rink lies along the building's long axis, which is an inference, and the arrow points north.", realMapped: "The rink lies as OpenStreetMap maps the ice, and the arrow points north.", realNone: "The rink's orientation is not known, so it is drawn east-west with north at the top.", stadium: "The stands are a plausible bowl of {seats} seats in {tiers} bowls, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the stadium's plan.", stadiumOpening: "The field lies along the opening in the roof, which is the size of the field, and the arrow points north.", stadiumBuilding: "The field lies along the building's long axis, which is an inference, and the arrow points north.", stadiumNone: "The field's orientation is not known, so it is drawn east-west with north at the top.", stadiumHall: "The stands are a plausible bowl of {seats} seats in {tiers} levels, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the hall's plan.", stadiumRingBuilding: "The ring is set along the building's long axis, which is an inference, and the arrow points north.", stadiumCourtBuilding: "The court is set along the building's long axis, which is an inference, and the arrow points north.", stadiumPitchPark: "The way home plate faces is worked out from the grass of the field OpenStreetMap maps, fitted to the published fence, and the arrow points north.", stadiumNonePark: "The way home plate faces is not known, so the field is drawn the way that fits the building, and no arrow points north.", stadiumPark: "The stands are the usual stands of a ballpark, each band as deep as its seats need, for {seats} seats, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the park's plan.", stadiumPitch: "The field lies as OpenStreetMap maps the pitch, and the arrow points north.", stadiumInfield: "The way home plate faces is worked out from the infield OpenStreetMap maps and the building's outline, and the arrow points north.", stadiumStreets: "The way home plate faces is worked out from the streets that the English Wikipedia lists at first base, left field, third base and right field, to the nearest 45 degrees, and the arrow points north.", district: "A plan of {count} real venues at their real positions and orientations, from the outlines OpenStreetMap maps; {mode}.", districtNeighbours: "it is fitted close round the buildings", districtCity: "it keeps the real distances, with a grid of kilometres and a scale", districtCredit: "Credit:", districtOutside: "People outside: {percent} percent of the seats of the venues, {people} people, stand on the sidewalks, plazas and grounds round them, and none on a road.", era: "The markings are those of the {season} season.", hammer: "{name} has the hammer.", ends: "By end, {name}: {list}.", total: "Total", wonPens: "{name} won {a}-{b} on penalties.", pens: "{a}-{b} pens", on: "Drawn as", sep: ", ", stop: ".", colon: ": ", gap: " " },
203
203
  budo: {
204
204
  sides: { north: "north", south: "south", east: "east", west: "west" },
205
205
  judo: "a judo contest area of {contest} by {contest} metres in a safety area {safety} metres wide, laid with tatami of {tatami}",
@@ -224,7 +224,7 @@ export const WORDS_JA = {
224
224
  },
225
225
  },
226
226
  scoreTitle: "{away} {awayScore}、{home} {homeScore}",
227
- said: { home: "ホーム", away: "アウェイ", won: "{name}の勝ちです。", tied: "同点です。", park: "外野フェンスは、{park}の公表されている距離をもとに描かれています。", officially: "正式名称は{name}です。", ground: "ピッチは、{ground}の公表されている広さをもとに描かれています。", groundDefault: "{ground}のピッチの広さは公表されていないため、国際標準の105×68mで描かれています。", arenaRink: "{arena}のアイスホッケーリンク", arenaCourt: "{arena}のバスケットボールコート", arenaRinkAbout: "アイスホッケーのリンク。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で角が丸く、レッドライン、ブルーライン、フェイスオフサークル、クリース、ゴールがあります", arenaCourtAbout: "バスケットボールのコート。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で、ペイントエリア、スリーポイントライン、フリースローサークル、センターサークルがあります", arenaSize: "広さは、{arena}の公表されている数値をもとに描かれています。", arenaUnknown: "{arena}のリンクはNHLの規格より短かったとされていますが、広さが示されていないため、NHLの規格で描かれています。", real: "スタンドは、OpenStreetMapの建物の輪郭((c) OpenStreetMap contributors)の内側に収まるように、{arena}の{seats}席・{tiers}層を推定して描いたもので、アリーナの設計図ではありません。", realBuilding: "リンクは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", realMapped: "リンクの向きはOpenStreetMapに記録された氷面のとおりで、矢印は北を指します。", realNone: "リンクの向きは分かっていないため、東西に置き、北を上にしています。", stadium: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層のスタンドを推定して描いています。スタジアムの設計図ではありません。", stadiumOpening: "フィールドは屋根の開口部(フィールドと同じ大きさ)に沿って置いており、矢印は北を指します。", stadiumBuilding: "フィールドは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumNone: "フィールドの向きは分かっていないため、東西に置き、北を上にしています。", stadiumHall: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層の客席を推定して描いています。ホールの設計図ではありません。", stadiumRingBuilding: "リングは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumCourtBuilding: "コートは建物の長軸に沿って置かれており(推定)、矢印は北を指します。", stadiumPitchPark: "ホームプレートの向きは、OpenStreetMapに記録されたグラウンドの芝生を、公表されているフェンスの形に合わせて求めており、矢印は北を指します。", stadiumNonePark: "ホームプレートの向きは分かっていないため、建物に収まる向きでグラウンドを描いており、北を示す矢印はありません。", stadiumPark: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席のスタンドを推定して描いています。球場の設計図ではありません。", stadiumPitch: "フィールドの向きはOpenStreetMapに記録されたピッチのとおりで、矢印は北を指します。", stadiumInfield: "ホームプレートの向きは、OpenStreetMapに記録された内野と建物の輪郭から推定しており、矢印は北を指します。", district: "OpenStreetMapの建物の輪郭をもとに、{count}か所の実在の会場を実際の位置と向きのとおりに並べた配置図です。{mode}。", districtNeighbours: "建物のすぐ近くまで寄せて描いています", districtCity: "実際の距離を保ち、1km方眼と縮尺を添えています", districtCredit: "出典:", districtOutside: "会場の外の人出:会場の全座席数の{percent}%にあたる{people}人が、会場の周りの歩道、広場、敷地に立っています。車道には立っていません。", era: "ラインやマークは{season}シーズンのものです。", hammer: "{name}がハンマー(最後の1投)を持っています。", ends: "{name}のエンドごとの得点:{list}。", total: "合計", wonPens: "{name}がPK戦を{a}-{b}で制しました。", pens: "PK戦 {a}-{b}", on: "図の内容", sep: "、", stop: "。", colon: ":", gap: "" },
227
+ said: { home: "ホーム", away: "アウェイ", won: "{name}の勝ちです。", tied: "同点です。", park: "外野フェンスは、{park}の公表されている距離をもとに描かれています。", officially: "正式名称は{name}です。", ground: "ピッチは、{ground}の公表されている広さをもとに描かれています。", groundDefault: "{ground}のピッチの広さは公表されていないため、国際標準の105×68mで描かれています。", arenaRink: "{arena}のアイスホッケーリンク", arenaCourt: "{arena}のバスケットボールコート", arenaRinkAbout: "アイスホッケーのリンク。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で角が丸く、レッドライン、ブルーライン、フェイスオフサークル、クリース、ゴールがあります", arenaCourtAbout: "バスケットボールのコート。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で、ペイントエリア、スリーポイントライン、フリースローサークル、センターサークルがあります", arenaSize: "広さは、{arena}の公表されている数値をもとに描かれています。", arenaUnknown: "{arena}のリンクはNHLの規格より短かったとされていますが、広さが示されていないため、NHLの規格で描かれています。", real: "スタンドは、OpenStreetMapの建物の輪郭((c) OpenStreetMap contributors)の内側に収まるように、{arena}の{seats}席・{tiers}層を推定して描いたもので、アリーナの設計図ではありません。", realBuilding: "リンクは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", realMapped: "リンクの向きはOpenStreetMapに記録された氷面のとおりで、矢印は北を指します。", realNone: "リンクの向きは分かっていないため、東西に置き、北を上にしています。", stadium: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層のスタンドを推定して描いています。スタジアムの設計図ではありません。", stadiumOpening: "フィールドは屋根の開口部(フィールドと同じ大きさ)に沿って置いており、矢印は北を指します。", stadiumBuilding: "フィールドは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumNone: "フィールドの向きは分かっていないため、東西に置き、北を上にしています。", stadiumHall: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層の客席を推定して描いています。ホールの設計図ではありません。", stadiumRingBuilding: "リングは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumCourtBuilding: "コートは建物の長軸に沿って置かれており(推定)、矢印は北を指します。", stadiumPitchPark: "ホームプレートの向きは、OpenStreetMapに記録されたグラウンドの芝生を、公表されているフェンスの形に合わせて求めており、矢印は北を指します。", stadiumNonePark: "ホームプレートの向きは分かっていないため、建物に収まる向きでグラウンドを描いており、北を示す矢印はありません。", stadiumPark: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席のスタンドを推定して描いています。球場の設計図ではありません。", stadiumPitch: "フィールドの向きはOpenStreetMapに記録されたピッチのとおりで、矢印は北を指します。", stadiumInfield: "ホームプレートの向きは、OpenStreetMapに記録された内野と建物の輪郭から推定しており、矢印は北を指します。", stadiumStreets: "ホームプレートの向きは、英語版ウィキペディアが一塁・左翼・三塁・右翼の位置に挙げている通りから、45度刻みで推定しており、矢印は北を指します。", district: "OpenStreetMapの建物の輪郭をもとに、{count}か所の実在の会場を実際の位置と向きのとおりに並べた配置図です。{mode}。", districtNeighbours: "建物のすぐ近くまで寄せて描いています", districtCity: "実際の距離を保ち、1km方眼と縮尺を添えています", districtCredit: "出典:", districtOutside: "会場の外の人出:会場の全座席数の{percent}%にあたる{people}人が、会場の周りの歩道、広場、敷地に立っています。車道には立っていません。", era: "ラインやマークは{season}シーズンのものです。", hammer: "{name}がハンマー(最後の1投)を持っています。", ends: "{name}のエンドごとの得点:{list}。", total: "合計", wonPens: "{name}がPK戦を{a}-{b}で制しました。", pens: "PK戦 {a}-{b}", on: "図の内容", sep: "、", stop: "。", colon: ":", gap: "" },
228
228
  budo: {
229
229
  sides: { north: "北", south: "南", east: "東", west: "西" },
230
230
  judo: "柔道の試合場。試合場内は{contest}m四方、幅{safety}mの安全地帯があり、{tatami}の畳を敷いています",
@@ -5,9 +5,9 @@ import { KYOGIJO_TURN_BOARD_STYLE } from "./overlay.js";
5
5
  * defaults are in `:where()`, which has no weight, so a page's own rule always wins; the colours are custom properties (`--ky-up-*`) a page can set.
6
6
  */
7
7
  export const KYOGIJO_TURN_STYLE = `
8
- :where(.ky-turn){--ky-left:#2f6db0;--ky-right:#b5452c;--ky-cr-home:#b5452c;--ky-cr-away:#2f6db0;--ky-cr-bed:#4a525d;--ky-cr-empty:#7d8794;--ky-cr-gap:#14181d;--ky-cr-conc:#8c929b;--ky-cr-dim:1;--ky-up-roof:rgba(214,218,212,.6);--ky-up-roofline:rgba(40,46,42,.55);--ky-up-fascia:#9aa09a;--ky-up-ivy:#5b8a45;--ky-up-brick:#a8604a;--ky-up-canopy:rgba(232,235,230,.9);--ky-up-door:#8a6a46;--ky-up-window:rgba(150,200,240,.55);--ky-up-sash:#f2f0e6;--ky-up-banner:#26211d;--ky-up-bannerline:#c8b88a;--ky-up-room:#efe8d6;--ky-up-roomback:#d6cdb9;--ky-up-ceiling:#dedcd5;--ky-up-railw:#fbfaf6;--ky-up-wainline:#6b4a2a;--ky-up-riser:#3f342c;--ky-up-slab:#d8cfba;--ky-up-slabedge:#c2b69c;--ky-up-seatc:#46342a;--ky-up-seatb:#2d211b;--ky-up-dais:#a0683a;--ky-up-dais-side:#6a4326;--ky-up-shrine:#c9b48f;--ky-up-shrine-side:#8f7a55;--ky-up-gal:#4d4037;--ky-up-aisle:#dcc196;--ky-up-panel:rgba(95,58,22,.3);--ky-up-alcove:#b58a52;--ky-up-purple:#5b2a6e;--ky-up-clock:#ffffff;--ky-up-rail:#3a2c1f;--ky-up-matedge:#2a2a26;--ky-up-sbframe:#1a1f1c;--ky-up-sbedge:#5b645e;--ky-up-sbline:#59615b;--ky-up-pole:#e9e7de;--ky-up-white:#f6f4ec;--ky-up-yellow:#f2c230;--ky-up-red:#c8372d;--ky-up-orange:#f26a1b;--ky-up-wood:#e3d4b4;--ky-up-leg:#474e49;--ky-up-pad:#2f6db0;--ky-up-cagepad:#2a2d32;--ky-up-table:#18243f;--ky-up-foot:rgba(0,0,0,.25);--ky-up-ground:#d9d5c7;--ky-up-wall:#2f4a36;--ky-up-clay:#a8703f;--ky-up-cap:#1d2a22;--ky-up-board:#ece9df;--ky-up-glass:rgba(138,190,232,.26);--ky-up-backboard:rgba(255,255,255,.88);--ky-up-outline:#f2f0e6;--ky-up-rim:#e8591a;--ky-up-mesh:rgba(24,30,27,.7);--ky-up-netfill:rgba(255,255,255,.2);--ky-up-tape:#fbfaf4;--ky-turn-bg:transparent;--ky-turn-ink:#1f2320;--ky-turn-rule:rgba(31,35,32,.22);--ky-turn-chip:rgba(255,255,255,.86);--ky-turn-chip-on:#1f2320;--ky-turn-chip-on-ink:#fff;${JUDGING_LIGHT};--ky-up-blk-a:#e9ece4;--ky-up-blk-b:#c9cdc3;--ky-up-blk-c:#a4aa9f;--ky-up-blk-roof:#f4f5f0;--ky-up-blk-line:rgba(40,46,42,.38);--ky-up-plate:rgba(47,109,176,.5);--ky-up-plate-line:#2f6db0}
9
- @media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-turn:not([data-theme=light])){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-ivy:#3d6633;--ky-up-brick:#7a4434;--ky-up-canopy:rgba(78,84,80,.9);--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK};--ky-up-blk-a:#59626b;--ky-up-blk-b:#444b53;--ky-up-blk-c:#30363c;--ky-up-blk-roof:#69727b;--ky-up-blk-line:rgba(220,226,220,.3);--ky-up-plate:rgba(127,176,232,.45);--ky-up-plate-line:#7fb0e8}}
10
- :where(html[data-theme=dark] .ky-turn,.ky-turn[data-theme=dark]){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-ivy:#3d6633;--ky-up-brick:#7a4434;--ky-up-canopy:rgba(78,84,80,.9);--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK};--ky-up-blk-a:#59626b;--ky-up-blk-b:#444b53;--ky-up-blk-c:#30363c;--ky-up-blk-roof:#69727b;--ky-up-blk-line:rgba(220,226,220,.3);--ky-up-plate:rgba(127,176,232,.45);--ky-up-plate-line:#7fb0e8}
8
+ :where(.ky-turn){--ky-up-saddle:#ecebe4;--ky-up-saddle-wall:#c6c9c3;--ky-up-saddle-beam:#d8dad4;--ky-left:#2f6db0;--ky-right:#b5452c;--ky-cr-home:#b5452c;--ky-cr-away:#2f6db0;--ky-cr-bed:#4a525d;--ky-cr-empty:#7d8794;--ky-cr-gap:#14181d;--ky-cr-conc:#8c929b;--ky-cr-dim:1;--ky-up-roof:rgba(214,218,212,.6);--ky-up-roofline:rgba(40,46,42,.55);--ky-up-fascia:#9aa09a;--ky-up-ivy:#5b8a45;--ky-up-brick:#a8604a;--ky-up-canopy:rgba(232,235,230,.9);--ky-up-door:#8a6a46;--ky-up-window:rgba(150,200,240,.55);--ky-up-sash:#f2f0e6;--ky-up-banner:#26211d;--ky-up-bannerline:#c8b88a;--ky-up-room:#efe8d6;--ky-up-roomback:#d6cdb9;--ky-up-ceiling:#dedcd5;--ky-up-railw:#fbfaf6;--ky-up-wainline:#6b4a2a;--ky-up-riser:#3f342c;--ky-up-slab:#d8cfba;--ky-up-slabedge:#c2b69c;--ky-up-seatc:#46342a;--ky-up-seatb:#2d211b;--ky-up-dais:#a0683a;--ky-up-dais-side:#6a4326;--ky-up-shrine:#c9b48f;--ky-up-shrine-side:#8f7a55;--ky-up-gal:#4d4037;--ky-up-aisle:#dcc196;--ky-up-panel:rgba(95,58,22,.3);--ky-up-alcove:#b58a52;--ky-up-purple:#5b2a6e;--ky-up-clock:#ffffff;--ky-up-rail:#3a2c1f;--ky-up-matedge:#2a2a26;--ky-up-sbframe:#1a1f1c;--ky-up-sbedge:#5b645e;--ky-up-sbline:#59615b;--ky-up-pole:#e9e7de;--ky-up-white:#f6f4ec;--ky-up-yellow:#f2c230;--ky-up-red:#c8372d;--ky-up-orange:#f26a1b;--ky-up-wood:#e3d4b4;--ky-up-leg:#474e49;--ky-up-pad:#2f6db0;--ky-up-cagepad:#2a2d32;--ky-up-table:#18243f;--ky-up-foot:rgba(0,0,0,.25);--ky-up-ground:#d9d5c7;--ky-up-wall:#2f4a36;--ky-up-clay:#a8703f;--ky-up-cap:#1d2a22;--ky-up-board:#ece9df;--ky-up-glass:rgba(138,190,232,.26);--ky-up-backboard:rgba(255,255,255,.88);--ky-up-outline:#f2f0e6;--ky-up-rim:#e8591a;--ky-up-mesh:rgba(24,30,27,.7);--ky-up-netfill:rgba(255,255,255,.2);--ky-up-tape:#fbfaf4;--ky-turn-bg:transparent;--ky-turn-ink:#1f2320;--ky-turn-rule:rgba(31,35,32,.22);--ky-turn-chip:rgba(255,255,255,.86);--ky-turn-chip-on:#1f2320;--ky-turn-chip-on-ink:#fff;${JUDGING_LIGHT};--ky-up-blk-a:#e9ece4;--ky-up-blk-b:#c9cdc3;--ky-up-blk-c:#a4aa9f;--ky-up-blk-roof:#f4f5f0;--ky-up-blk-line:rgba(40,46,42,.38);--ky-up-plate:rgba(47,109,176,.5);--ky-up-plate-line:#2f6db0}
9
+ @media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-turn:not([data-theme=light])){--ky-up-saddle:#aeb1ab;--ky-up-saddle-wall:#7e847f;--ky-up-saddle-beam:#979c97;--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-ivy:#3d6633;--ky-up-brick:#7a4434;--ky-up-canopy:rgba(78,84,80,.9);--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK};--ky-up-blk-a:#59626b;--ky-up-blk-b:#444b53;--ky-up-blk-c:#30363c;--ky-up-blk-roof:#69727b;--ky-up-blk-line:rgba(220,226,220,.3);--ky-up-plate:rgba(127,176,232,.45);--ky-up-plate-line:#7fb0e8}}
10
+ :where(html[data-theme=dark] .ky-turn,.ky-turn[data-theme=dark]){--ky-up-saddle:#aeb1ab;--ky-up-saddle-wall:#7e847f;--ky-up-saddle-beam:#979c97;--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-ivy:#3d6633;--ky-up-brick:#7a4434;--ky-up-canopy:rgba(78,84,80,.9);--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK};--ky-up-blk-a:#59626b;--ky-up-blk-b:#444b53;--ky-up-blk-c:#30363c;--ky-up-blk-roof:#69727b;--ky-up-blk-line:rgba(220,226,220,.3);--ky-up-plate:rgba(127,176,232,.45);--ky-up-plate-line:#7fb0e8}
11
11
  .ky-turn{position:relative;display:block;width:100%;color:var(--ky-turn-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);background:var(--ky-turn-bg)}
12
12
  .ky-turn-view{position:relative;isolation:isolate;width:100%;aspect-ratio:var(--ky-turn-ratio,16/10);min-height:260px;overflow:hidden;touch-action:pan-y;cursor:grab;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;border-radius:12px;outline-offset:2px}
13
13
  .ky-turn[data-touch=turn] .ky-turn-view{touch-action:none}
@@ -94,14 +94,28 @@ export const KYOGIJO_TURN_STYLE = `
94
94
  .ky-turn .u-proxy-c{fill:color-mix(in srgb,var(--ky-up-blk-c) 52%,#324b72)}
95
95
  .ky-turn .u-proxy-roof{fill:color-mix(in srgb,var(--ky-up-blk-roof) 60%,#5f7fae);stroke:var(--ky-up-blk-line);stroke-width:.6;stroke-linejoin:round}
96
96
  .ky-turn .u-roof{fill:var(--ky-up-roof);stroke:var(--ky-up-roofline);stroke-width:.8;stroke-linejoin:round}
97
+ /* The Saddledome's building (venue.saddle.ts): the roof's net of quads, its beam, the pier between two panels, and the veils of shade and light that give each facet its tone. */
98
+ .ky-turn .u-saddle{fill:var(--ky-up-saddle);stroke:var(--ky-up-roofline);stroke-width:.3;stroke-linejoin:round}
99
+ .ky-turn .u-saddle-beam{fill:var(--ky-up-saddle-beam);stroke:var(--ky-up-roofline);stroke-width:.4;stroke-linejoin:round}
100
+ .ky-turn .u-saddle-wall{fill:var(--ky-up-saddle-wall);stroke:var(--ky-up-roofline);stroke-width:.3;stroke-linejoin:round}
101
+ .ky-turn .u-pier{fill:none;stroke:color-mix(in srgb,var(--ky-up-white) 55%,var(--ky-up-fascia));stroke-width:3}
102
+ .ky-turn .u-clad{stroke:var(--ky-up-roofline);stroke-width:.3;stroke-opacity:.5}
103
+ .ky-turn .u-stair-round{fill:var(--ky-up-yellow);stroke:var(--ky-up-roofline);stroke-width:.4}
104
+ .ky-turn .u-stair-square{fill:var(--ky-up-red);stroke:var(--ky-up-roofline);stroke-width:.4}
105
+ .ky-turn .u-shade{fill:#000}
106
+ .ky-turn .u-lite{fill:#fff}
97
107
  .ky-turn .u-signplate{fill:var(--ky-turn-bg);fill-opacity:.9;stroke:var(--ky-turn-rule);stroke-width:.5}
98
108
  .ky-turn .u-sign{fill:var(--ky-turn-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);font-weight:600}
99
109
  .ky-turn .u-mast{fill:var(--ky-up-fascia);stroke:var(--ky-up-roofline);stroke-width:.4}
100
- .ky-turn .u-tower{fill:var(--ky-up-fascia);stroke:var(--ky-up-roofline);stroke-width:.4}
110
+ .ky-turn .u-tower{fill:color-mix(in srgb,var(--ky-up-fascia) 70%,var(--ky-up-white));stroke:var(--ky-up-roofline);stroke-width:.4}
101
111
  .ky-turn .u-tower-slit{fill:var(--ky-up-roofline);fill-opacity:.55}
112
+ .ky-turn .u-rib{fill:color-mix(in srgb,var(--ky-up-fascia) 40%,var(--ky-up-white));stroke:var(--ky-up-roofline);stroke-width:.5}
113
+ .ky-turn .u-bay{fill:color-mix(in srgb,var(--ky-up-fascia) 55%,var(--ky-up-white));stroke:none}
114
+ .ky-turn .u-dome{fill:color-mix(in srgb,var(--ky-up-fascia) 22%,var(--ky-up-white));stroke:var(--ky-up-roofline);stroke-width:.5;stroke-linejoin:round}
115
+ .ky-turn .u-glass{fill:#1b252d;fill-opacity:.8}
102
116
  .ky-turn .u-tower-foot{fill:var(--ky-up-roofline);fill-opacity:.8}
103
- .ky-turn .u-tower-cap{fill:var(--ky-up-roofline);fill-opacity:.85}
104
- .ky-turn .u-tower-glass{fill:var(--ky-up-fascia);fill-opacity:.55}
117
+ .ky-turn .u-tower-cap{fill:color-mix(in srgb,var(--ky-up-fascia) 60%,var(--ky-up-white));stroke:var(--ky-up-roofline);stroke-width:.4}
118
+ .ky-turn .u-tower-glass{fill:#0b1a24;fill-opacity:.5}
105
119
  .ky-turn .u-tower-cable{fill:var(--ky-up-roofline);fill-opacity:.7}
106
120
  .ky-turn .u-skylight{fill:var(--ky-up-skylight,#a9c8da);fill-opacity:.9;stroke:var(--ky-up-roofline);stroke-width:.5}
107
121
  .ky-turn .u-podium{fill:#000;fill-opacity:.16}
@@ -1,12 +1,9 @@
1
1
  import { type Pt, type RealShell, type StandRing } from "./crowd.ts";
2
2
  import { type Plane, type Upright, type V3 } from "./turn.kit.ts";
3
- /** How far a lid rises from its edge towards the middle, and how far a saddle's high ends stand over its low sides, in feet (ours: the sources give a roof's kind, not its height). */
3
+ /** How far a lid rises from its edge towards the middle, in feet (ours: the sources give a roof's kind, not its height). */
4
4
  export declare const LID_RISE = 22;
5
- export declare const SADDLE_RISE = 30;
6
5
  /** How far a barrel vault's crown stands over its springing at the long sides, in feet (ours: the article says the roof looks like a Quonset hut, not how high it rises). */
7
6
  export declare const BARREL_RISE = 34;
8
- /** The saddle's share of its rise at a point (0 at the middle of a side, 1 at the middle of an end): half a saddle's way from one to the other, as a hyperbolic paraboloid (u squared less v squared) lies over the building's box. */
9
- export declare function saddleAt(real: Pick<RealShell, "footprint">, [x, y]: Pt): number;
10
7
  /** The barrel vault's share of its rise at a point (0 at the springing along each long side, 1 over the middle line): a round arch over the building's short axis, the quarter of a circle's rise at each step in from the side. */
11
8
  export declare function barrelAt(real: Pick<RealShell, "footprint">, [, y]: Pt): number;
12
9
  /** The height of the roof over a point of the building's edge, above the surface, in feet. */
@@ -14,12 +11,12 @@ export declare const roofAt: (real: RealShell, p: Pt) => number;
14
11
  /** The height of the wall of a building under canopies at a point of its rim: where a canopy is over it, the canopy's back edge, which the wall carries up to (the outside of Lumen Field's roofs is one curved face to the ground); elsewhere the rim's own height. */
15
12
  export declare function wallAt(real: RealShell, p: Pt): number;
16
13
  /**
17
- * A tower that leans and curves: a square shaft of horizontal sections that tapers from a broad foot on the ground to a narrow top, along a curve that runs from the foot to the top (the line the map gives of its funicular), its
18
- * back curving up from a flatter foot to the inclination the article gives at the top; a plinth where it meets the ground, the dark slits down its sloped back, an observatory of dark and glass bands on its top and, with `cables`,
19
- * thin lines from the observatory to the roof. Each face of a ring is a trapezoid on a plane through its corners, so that the faces meet along their edges. The foot and the top and their height are the sources'; the curve, the
20
- * widths, the slits, the plinth and the observatory are ours.
14
+ * A tower that leans and curves: a shaft of horizontal sections, a square of rising width at its foot narrowing to its top, that leans over the building. Its foot is at the building's rim (where the mapped axis crosses it, `rim` feet
15
+ * from the top along the axis), its front, the steep face that looks at the building, runs from there to the top, and its back, the face that looks away and up, sweeps down from the top in a long curve that flattens to the ground
16
+ * far behind the foot. On the top stands the observatory, a dark block with a band of glass, and, with `cables`, thin lines run from it to the roof. Every face of a ring is a trapezoid on a plane through its corners, so the faces
17
+ * meet along their edges. The foot, the top and the height are the sources' (the map's funicular line and the article's inclination); the curve, the widths, the slits and the observatory are ours.
21
18
  */
22
- export declare function towerPlanes(tower: NonNullable<RealShell["tower"]>, z: number, cables?: V3[]): Plane[];
19
+ export declare function towerPlanes(tower: NonNullable<RealShell["tower"]>, z: number, cables?: V3[], rim?: number): Plane[];
23
20
  /** The point where a ray from `from` first meets a polygon, or `undefined`. */
24
21
  export declare function firstHit(poly: readonly Pt[], from: Pt, angle: number): Pt | undefined;
25
22
  /** How tall the glazed concourse band over a podium is, in feet (ours). */