@johnmorrisdotca/kyogijo-stadiums 0.57.0 → 0.59.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 (62) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/NOTICE.md +2 -0
  3. package/README.md +1 -1
  4. package/catalogue.json +1 -1
  5. package/dist/areas.d.ts +8 -3
  6. package/dist/areas.js +14 -5
  7. package/dist/athletics/plan.d.ts +2 -0
  8. package/dist/athletics/plan.js +20 -4
  9. package/dist/catalogue.data.js +1 -1
  10. package/dist/circuits-entry.d.ts +1 -1
  11. package/dist/circuits-entry.js +1 -1
  12. package/dist/circuits.draw.js +7 -4
  13. package/dist/circuits.held.d.ts +1 -0
  14. package/dist/circuits.held.js +13 -0
  15. package/dist/circuits.index.d.ts +15 -0
  16. package/dist/circuits.index.js +28 -0
  17. package/dist/circuits.race.dom.d.ts +25 -5
  18. package/dist/circuits.race.dom.js +22 -7
  19. package/dist/circuits.race.draw.d.ts +1 -1
  20. package/dist/circuits.race.draw.js +4 -4
  21. package/dist/circuits.turn.d.ts +8 -0
  22. package/dist/circuits.turn.js +29 -21
  23. package/dist/circuits.types.d.ts +2 -0
  24. package/dist/circuits.words.d.ts +4 -0
  25. package/dist/circuits.words.js +6 -0
  26. package/dist/cityctx-entry.d.ts +1 -1
  27. package/dist/cityctx-entry.js +1 -1
  28. package/dist/cityctx.d.ts +2 -2
  29. package/dist/cityctx.data.d.ts +2 -1
  30. package/dist/cityctx.data.js +3 -2
  31. package/dist/cityctx.js +9 -3
  32. package/dist/cityctx.las-vegas.data.d.ts +3 -0
  33. package/dist/cityctx.las-vegas.data.js +1387 -0
  34. package/dist/context.d.ts +11 -1
  35. package/dist/context.js +18 -4
  36. package/dist/crowd.d.ts +18 -0
  37. package/dist/crowd.js +88 -18
  38. package/dist/district-entry.d.ts +1 -0
  39. package/dist/district-entry.js +2 -0
  40. package/dist/district.api.d.ts +9 -2
  41. package/dist/district.api.js +7 -1
  42. package/dist/district.circuit.d.ts +34 -0
  43. package/dist/district.circuit.js +13 -0
  44. package/dist/district.d.ts +27 -3
  45. package/dist/district.js +85 -15
  46. package/dist/district.race.d.ts +40 -0
  47. package/dist/district.race.js +60 -0
  48. package/dist/district.turn.js +27 -1
  49. package/dist/draw.js +22 -7
  50. package/dist/landmarks.d.ts +105 -0
  51. package/dist/landmarks.js +160 -0
  52. package/dist/surfaces/artistic.js +3 -1
  53. package/dist/surfaces/athletics.js +2 -0
  54. package/dist/surfaces/hockey.js +3 -1
  55. package/dist/surfaces/surface.d.ts +7 -0
  56. package/dist/surfaces/surface.js +9 -0
  57. package/dist/turn.dom.js +5 -5
  58. package/dist/turn.markup.js +12 -1
  59. package/dist/turn.style.js +1 -0
  60. package/dist/version.d.ts +1 -1
  61. package/dist/version.js +1 -1
  62. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.59.0] - 2026-11-03
11
+
12
+ ### Fixed
13
+
14
+ - **The stands clear the whole floor, centred (KYOGIJO-STADIUMS-44)** ([the stands and the crowd](./docs/crowd.md#the-venues)): with `crowd`, the bowl's opening was laid out from the playing surface's box alone, so the floor drawn round it ran into the first rows: a court's run-off at the corners (volleyball, sepak takraw, netball), a table tennis area at its ends, a rink's boards, and an athletics track's sprint straight, jumps' runways and pits beyond the oval. The opening is now fitted to everything the surface draws round its playing area (its view with no margin, and an outline where the floor is not a rectangle: the track with its straight and runways, a rink's rounded boards), clear of it all round, corners included, by 2 feet; a floor bigger than the sport's distances (table tennis) gets an opening of its own shape. The bench side's extra room is shared by the two sides, so the opening is centred on the floor and the bowl in the drawing (a soccer ground's touchlines are 23.7 feet from the first row on both sides, where they were 19.7 and 27.7), and the margin holds the roof's shade. The tunnel, the camera platform and the media's desks stay in the seats, the media's desks no longer run round a small opening's corners, and the benches stand between the floor and the first row only where the gap holds them. An artistic swimming area with a crowd keeps the margin the stands need along its sides (it gave them four tenths of it). The flat drawing and the 3D view share the one layout (`crowdStands`), which now gives the `floor` it was fitted to. In 3D, a table tennis table's raised floor (the drawing lifted to the table's height, with the ground the stands stand on) was sorted by its middle with the stands and drawn over the far ones, so from Low corner or Broadcast the bowl looked cut open round a small tile: the bowl is now always drawn over a raised floor, and the faint near stands at a low viewpoint are the walls giving way to the eye, as before. New tests walk every sport with a crowd, at every size and orientation, light and dark: no part of the floor's outline within 2 feet of a seat, nothing of the stands on the floor, the opening centred on the floor and the bowl centred in the drawing and wholly inside it; and in 3D, at every viewpoint, zoom and box, the eye outside the whole scene, nothing near the eye's plane, and no plane of the bowl under the floor. Pictures of a court, a track, a rink, a ring and a table tennis area in their stands are in the docs.
15
+
16
+ ## [0.58.0] - 2026-11-03
17
+
18
+ ### Added
19
+
20
+ - **Las Vegas: the Strip Circuit, T-Mobile Arena and the Strip's landmarks on one ground (KYOGIJO-STADIUMS-30)** ([districts](./docs/district.md#las-vegas-a-circuit-on-the-ground-and-the-strips-landmarks)): a district's `circuit` option lays a circuit on its ground with the venues, turned as it lies, its pit building and turns' numbers standing in 3D (`circuitPointIn` maps a point of the circuit's plan into the district's, for a race on it); a circuit alone is a district too. A new area of context, `LAS_VEGAS` (`las-vegas`, OpenStreetMap, ODbL), 1.9 km round the lap and the arena: the Strip's buildings and towers at their mapped heights, the streets and the water, and four landmarks by shapes of their own at published sizes, named in English and Japanese (`ContextArea.landmarks`, `landmarks.ts`): the Sphere a dome, the High Roller a wheel, the jets of the Fountains of Bellagio, and Caesars Palace's name over its tallest building. `areaKeysFor` and `loadAreasFor` take the circuit; the lean district reads the circuits with `loadCircuits()` (`CIRCUIT_INDEX` names them). Held to 2,100 planes in 3D (under Seattle's three venues) and 2,565 KB for the demo's page. The demo's district page has the preset "Las Vegas: the Strip Circuit, T-Mobile Arena and the Sphere".
21
+ - **A race on the Strip (KYOGIJO-STADIUMS-30)** ([a race on the Strip](./docs/district.md#a-race-on-the-strip)): the circuits' race runs on a district's ground, its twenty made-up cars on the Strip Circuit among T-Mobile Arena, the hotels, the Sphere, the High Roller and the Fountains of Bellagio, flat or in 3D, with the race clock, the speeds, the slider and the timing board. `drawDistrictRace` draws a moment and `mountDistrictRace` plays it in a page (a feed's race too, as `settings.race`); the race's view in a page takes the scene it is drawn in (`mountRaceIn`, `RaceScene`), and the cars are put on the district's ground with `circuitPointIn`. The demo's district page has **Race** beside the view when a circuit is chosen, kept in the address, and the circuits page's link to the district takes the race along. The lean district reads the race with the circuits (`loadCircuits`).
22
+ - Fixed: a 3D view in `auto` quality times its frames in the view's one shared animation frame: a district's view that turned or played a race while its quality was being chosen asked for two frames a frame.
23
+
10
24
  ## [0.57.0] - 2026-11-03
11
25
 
12
26
  ### Changed
package/NOTICE.md CHANGED
@@ -52,6 +52,8 @@ picture or code of the sources is copied; each source is named here so that the
52
52
  | `walk`: the ground a crowd outside a district's venues may stand on and what stops it (`WALK_DATA`, `src/walk.data.ts`): paths and sidewalks, plazas, squares, parks and platforms, and carriageways, cycle tracks, railways, water and buildings, as lines with a width and as rings | OpenStreetMap, through the Overpass API: every way and relation of the highways, buildings, railways, water, squares, parks and platforms within 350 metres of the district's venues, read on 2026-10-22, with the raw answer kept in `data-sources/walk/2026-10-22/`; laid flat in metres, simplified to 30 centimetres and written in quarter-metres, by the rules of `scripts/walk-rules.mjs` (no name, id or tag is copied) | ODbL 1.0, (c) OpenStreetMap contributors (https://www.openstreetmap.org/copyright): a derived database, kept in its own file and offered under the ODbL; a crowd or a drawing made from it is a produced work and carries the credit (`VENUE_ATTRIBUTION`) |
53
53
  | `plateau`: the buildings round Tokyo Dome City (`TOKYO_DOME_CITY`, `src/plateau.data.ts`): each building's roof-edge outline simplified to 0.6 metres on a half-metre grid, its height and, for a pitched LOD2 roof, the rise of the roof | Project PLATEAU, the Ministry of Land, Infrastructure, Transport and Tourism's (MLIT) 3D city model of Bunkyo ward, 2025 edition (product specification 5.0, CityGML, https://www.geospatial.jp/ckan/dataset/plateau-13105-bunkyo-ku-2025), read on 2026-10-22: the buildings within 500 metres of the middle of the district, with the raw fields kept in `data-sources/plateau/2026-10-22/` (`pnpm plateau:fetch`, `pnpm plateau`) | PLATEAU Site Policy (https://www.mlit.go.jp/plateau/site-policy/), clause 3: free use, commercial use included, compatible with CC BY 4.0 and the Public Data License 1.0; the source is credited and the data says it was edited and not made by MLIT (`PLATEAU_ATTRIBUTION`: "Project PLATEAU, Ministry of Land, Infrastructure, Transport and Tourism (MLIT), https://www.mlit.go.jp/plateau/ (3D city model of Bunkyo ward, 2025 edition), simplified and edited by Kyogijo Stadiums; not made by MLIT"); the copyright of a ward's data belongs to its local government. Kept in its own file, apart from the OpenStreetMap data (ODbL) and the CC0 files |
54
54
  | `cityctx`: the surroundings of the venues of Seattle (`SEATTLE_SODO`, `SEATTLE_CENTER`, `SEATTLE_CITY`) and the land cover and trees round Vancouver's (`VANCOUVER_NATURE`), in `src/cityctx.data.ts`: buildings (outline, height, for a pitched roof its rise), streets, railways, sidewalks and paths as lines with a width, water, green and wood outlines, and single trees | OpenStreetMap, through the Overpass API: the buildings (with their `height` and `building:levels`), the highways and railways, the tree nodes and rows within the radius of each area, read on 2026-10-22, the raw answers kept in `data-sources/cityctx/2026-10-22/` (`pnpm cityctx:fetch`, `pnpm cityctx:vox`, `pnpm cityctx`); laid flat in metres, simplified and put on a grid of half a metre (two metres for Seattle's city view) | OpenStreetMap's data is ODbL 1.0 (https://opendatacommons.org/licenses/odbl/1-0/), credited "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright"; the file is a derived database kept in its own file, apart from the CC0 files and from PLATEAU's data |
55
+ | `cityctx`: the surroundings of the Las Vegas Strip Circuit and T-Mobile Arena (`LAS_VEGAS`, `src/cityctx.las-vegas.data.ts`): the buildings and the tall parts of buildings that matter from far, the streets and railways, the water, and four landmarks' outlines (the Sphere way/976405284, the High Roller way/402543930, the Fountains of Bellagio relation/19511963, Caesars Palace way/495449303) | OpenStreetMap, through the Overpass API (every building and every part of a building with a `height` or `building:levels`, the highways, railways, water and parks, and the landmarks' elements), read on 2026-11-03 and kept in `data-sources/cityctx/2026-11-03/` (`pnpm cityctx:fetch`, `pnpm cityctx`) | ODbL 1.0, (c) OpenStreetMap contributors; a derived database kept apart from the CC0 files |
56
+ | `cityctx`: the landmarks' names and sizes (`LAS_VEGAS.landmarks`) | Wikidata's labels in English and Japanese and its heights (P2048) of Q60749353 (Sphere), Q892103 (High Roller), Q2735117 (Fountains of Bellagio) and Q569978 (Caesars Palace); the widths and the fountains' height from the English Wikipedia articles "Sphere (venue)" (revision 1382276386), "High Roller (Ferris wheel)" (1344683153) and "Fountains of Bellagio" (1381701116), kept as evidence in `data-sources/cityctx/2026-11-03/landmarks-las-vegas.json`; two Japanese names are ours (`スフィア`, `ベラージオの噴水`) | Wikidata is CC0; the figures read from the articles are facts, in our own words (the articles are CC BY-SA 4.0) |
55
57
  | `cityctx`: the surroundings of New York's venues (`NYC_MIDTOWN`, `NYC_BRONX`, `NYC_FLUSHING`, `NYC_BROOKLYN`), of the Meadowlands (`MEADOWLANDS`), of Belmont Park (`BELMONT_PARK`) and of Montreal's (`MONTREAL_DOWNTOWN`, `MONTREAL_OLYMPIC`, `MONTREAL_CITY`), one file each (`src/cityctx.<area>.data.ts`): buildings, streets, railways, sidewalks and paths, water, green and wood outlines, and single trees | OpenStreetMap, through the Overpass API (the buildings with their `height` and `building:levels`, the highways, railways, water and parks, the tree nodes and rows), read on 2026-10-30 and kept in `data-sources/cityctx/2026-10-30/` (`pnpm cityctx:fetch`, `pnpm cityctx`); the water, wetland, park, grass and wood outlines are OpenStreetMap's as mapped (VoxCity is not used for these areas) | OpenStreetMap's data is ODbL 1.0, credited "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright"; each file is a derived database kept apart from the CC0 files and from PLATEAU's data |
56
58
  | `cityctx`: the heights of New York's buildings | The City of New York's Building Footprints (NYC Open Data, dataset 5zhs-2jue, Office of Technology and Innovation: the outline, the roof height above the ground in feet and the construction year of each building), read through the portal's API on 2026-10-30 and kept in `data-sources/cityctx/2026-10-30/nyc-footprints-*.json`; the roof height is used where a footprint matches an OpenStreetMap building, and a footprint OpenStreetMap lacks is a building of its own | A public data set of the City under its Open Data Law (Administrative Code 23-502: made available without registration, licence requirement or restriction on use, the City being able to ask a third party that publishes it to name its source and version and to say what it changed); the City gives no warranty of the data's accuracy (the credit says it): the credit names the data set, the date read and that it was edited |
57
59
  | `cityctx`: Vancouver's trees | The City of Vancouver's open data, "Public trees" (place and diameter), read through the portal's API on 2026-10-22 and kept in `data-sources/cityctx/2026-10-22/` | The Open Government Licence - Vancouver (https://opendata.vancouver.ca/pages/licence/): free to use, with the City credited, in each layer, in English and in Japanese |
package/README.md CHANGED
@@ -254,7 +254,7 @@ console.log(svg.startsWith("<svg"), svg.includes("USA 3, Canada 2"));
254
254
  - **A catalogue of venues**: every venue drawn today, the ones being built and the ones wanted (every NBA, MLB, NFL, CFL and NHL venue of the season, world soccer, cricket, rugby, the Olympics, Formula One and more), with ids, names in English and Japanese and Wikidata items, as a [JSON file](https://johnmorrisdotca.github.io/kyogijo-stadiums/catalogue.json) and a typed entry. [The catalogue](./docs/catalogue.md).
255
255
  - **Real venues**: arenas and stadiums in the outline of their buildings, districts of several venues on one plan, the crowd in the stands and outside. [Ballparks](./docs/ballparks.md), [venues](./docs/venues.md), [districts](./docs/district.md).
256
256
  - **A 3D view** of any surface or venue that turns by mouse, touch or keys, with the roof and the walls to switch and a quality level for every device. [The 3D view](./docs/turn.md), [quality](./docs/quality.md).
257
- - **Circuits**: a famous race track as OpenStreetMap maps it, the Las Vegas Strip Circuit first, with its turns numbered and its pit building standing in 3D, and a simulated race on it: twenty made-up cars over a full race distance, with a race clock, a timing board and 1×, 10× and 60×. [Circuits](./docs/circuits.md#a-race).
257
+ - **Circuits**: a famous race track as OpenStreetMap maps it, the Las Vegas Strip Circuit first, with its turns numbered and its pit building standing in 3D, and a simulated race on it: twenty made-up cars over a full race distance, with a race clock, a timing board and 1×, 10× and 60×; on a district's ground beside T-Mobile Arena among the Strip, with the Sphere, the High Roller and the Fountains of Bellagio, where the race runs too. [Circuits](./docs/circuits.md#a-race), [Las Vegas](./docs/district.md#las-vegas-a-circuit-on-the-ground-and-the-strips-landmarks), [a race on the Strip](./docs/district.md#a-race-on-the-strip).
258
258
  - **Bring your own race feed**: a race plays from a feed you choose, read by your own browser from an address, pasted or from a file, and followed while it is live: kyogijo's open race timeline or OpenF1-style lap rows, with our made-up sample feed first. [The race feed](./docs/race-feed.md), [Where to find a feed](./docs/where-to-find-a-feed.md).
259
259
  - **Tournaments**: several surfaces on one floor, who judges where, boards laid over a surface. [Layouts](./docs/multi.md), [judging](./docs/judging.md), [boards](./docs/boards.md).
260
260
  - **Data on a surface**: points, heat maps, shot and pass charts, team shapes and a replay, flat or in 3D with the players standing on the ice, in 48 providers' coordinate frames. [Layers](./docs/layers.md), [frames](./docs/frames.md), [replay](./docs/replay.md), [an example game](./docs/example-games.md).
package/catalogue.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "generatedAt": "2026-11-03",
4
4
  "package": {
5
5
  "name": "@johnmorrisdotca/kyogijo-stadiums",
6
- "version": "0.57.0"
6
+ "version": "0.59.0"
7
7
  },
8
8
  "address": "https://johnmorrisdotca.github.io/kyogijo-stadiums/catalogue.json",
9
9
  "site": "https://johnmorrisdotca.github.io/kyogijo-stadiums/",
package/dist/areas.d.ts CHANGED
@@ -9,6 +9,11 @@ export declare const CITYCTX_VENUES: Readonly<{
9
9
  export declare const CITYCTX_DETAIL: Readonly<Record<string, readonly string[]>>;
10
10
  /** The most patches of New York's surroundings a choice of venues takes: more than this many venues of the city are too far apart for patches of detail round each. */
11
11
  export declare const MOST_DETAIL_AREAS = 2;
12
+ /** The venues and the circuits each area of a circuit's surroundings is drawn round: Las Vegas's (the Strip Circuit, T-Mobile Arena and the Strip between them, with the Sphere, the High Roller and the Fountains of Bellagio). */
13
+ export declare const CIRCUIT_AREAS: Readonly<Record<string, {
14
+ venues: readonly string[];
15
+ circuits: readonly string[];
16
+ }>>;
12
17
  /** The venues of Tokyo Dome City, round which Project PLATEAU's buildings are drawn (the Kodokan stands a little way off, inside the area). */
13
18
  export declare const TOKYO_VENUES: readonly string[];
14
19
  /** The key of every area there is data for, in the order a district draws them: each is one file. */
@@ -16,7 +21,7 @@ export declare const AREA_KEYS: readonly string[];
16
21
  /**
17
22
  * The keys of the areas that go with some venues, in the order to draw them: downtown Vancouver's buildings and streets and its water, green and trees for BC Place and Rogers Arena; Seattle's SoDo for Lumen Field and
18
23
  * T-Mobile Park, Seattle Center's for Climate Pledge Arena, and the city's own for a district that holds venues of both (they are three kilometres apart, so the detail of either would be a speck); Tokyo Dome City's for
19
- * Tokyo Dome, Korakuen Hall and the Kodokan, which take it before any other. A venue of none of these has no area.
24
+ * Tokyo Dome, Korakuen Hall and the Kodokan, which take it before any other; Las Vegas's for T-Mobile Arena and for the Las Vegas Strip Circuit (`circuit`, a district's circuit). A venue of none of these has no area.
20
25
  * @example
21
26
  * ```ts
22
27
  * import { areaKeysFor } from "@johnmorrisdotca/kyogijo-stadiums/areas";
@@ -24,7 +29,7 @@ export declare const AREA_KEYS: readonly string[];
24
29
  * // [ 'vancouver', 'vancouver-nature' ]
25
30
  * ```
26
31
  */
27
- export declare function areaKeysFor(venues: readonly string[]): string[];
32
+ export declare function areaKeysFor(venues: readonly string[], circuit?: string): string[];
28
33
  /**
29
34
  * One area by its key (`AREA_KEYS`), read from its own file the first time and kept after: asking again gives the same promise. A key that is not an area rejects, naming the keys there are.
30
35
  * @example
@@ -47,4 +52,4 @@ export declare function loadArea(key: string): Promise<ContextArea>;
47
52
  * console.log(plan.context?.attribution.en.length);
48
53
  * ```
49
54
  */
50
- export declare function loadAreasFor(venues: readonly string[]): Promise<ContextArea[]>;
55
+ export declare function loadAreasFor(venues: readonly string[], circuit?: string): Promise<ContextArea[]>;
package/dist/areas.js CHANGED
@@ -17,10 +17,14 @@ export const CITYCTX_DETAIL = Object.freeze({
17
17
  });
18
18
  /** The most patches of New York's surroundings a choice of venues takes: more than this many venues of the city are too far apart for patches of detail round each. */
19
19
  export const MOST_DETAIL_AREAS = 2;
20
+ /** The venues and the circuits each area of a circuit's surroundings is drawn round: Las Vegas's (the Strip Circuit, T-Mobile Arena and the Strip between them, with the Sphere, the High Roller and the Fountains of Bellagio). */
21
+ export const CIRCUIT_AREAS = Object.freeze({
22
+ "las-vegas": { venues: ["t-mobile-arena"], circuits: ["las-vegas"] },
23
+ });
20
24
  /** The venues of Tokyo Dome City, round which Project PLATEAU's buildings are drawn (the Kodokan stands a little way off, inside the area). */
21
25
  export const TOKYO_VENUES = Object.freeze(["tokyo-dome", "korakuen-hall", "kodokan"]);
22
26
  /** The key of every area there is data for, in the order a district draws them: each is one file. */
23
- export const AREA_KEYS = Object.freeze(["vancouver", "vancouver-nature", "seattle-sodo", "seattle-center", "seattle-city", "tokyo-dome-city", "nyc-midtown", "nyc-bronx", "nyc-flushing", "nyc-brooklyn", "meadowlands", "belmont-park", "montreal-downtown", "montreal-olympic", "montreal-city"]);
27
+ export const AREA_KEYS = Object.freeze(["vancouver", "vancouver-nature", "seattle-sodo", "seattle-center", "seattle-city", "tokyo-dome-city", "nyc-midtown", "nyc-bronx", "nyc-flushing", "nyc-brooklyn", "meadowlands", "belmont-park", "montreal-downtown", "montreal-olympic", "montreal-city", "las-vegas"]);
24
28
  /** Each area's loader: the one `import()` of the one file that holds it. */
25
29
  const LOADERS = {
26
30
  "vancouver": async () => (await import("./osm.context.data.js")).VANCOUVER_CENTRE,
@@ -38,11 +42,12 @@ const LOADERS = {
38
42
  "montreal-downtown": async () => (await import("./cityctx.montreal-downtown.data.js")).MONTREAL_DOWNTOWN,
39
43
  "montreal-olympic": async () => (await import("./cityctx.montreal-olympic.data.js")).MONTREAL_OLYMPIC,
40
44
  "montreal-city": async () => (await import("./cityctx.montreal-city.data.js")).MONTREAL_CITY,
45
+ "las-vegas": async () => (await import("./cityctx.las-vegas.data.js")).LAS_VEGAS,
41
46
  };
42
47
  /**
43
48
  * The keys of the areas that go with some venues, in the order to draw them: downtown Vancouver's buildings and streets and its water, green and trees for BC Place and Rogers Arena; Seattle's SoDo for Lumen Field and
44
49
  * T-Mobile Park, Seattle Center's for Climate Pledge Arena, and the city's own for a district that holds venues of both (they are three kilometres apart, so the detail of either would be a speck); Tokyo Dome City's for
45
- * Tokyo Dome, Korakuen Hall and the Kodokan, which take it before any other. A venue of none of these has no area.
50
+ * Tokyo Dome, Korakuen Hall and the Kodokan, which take it before any other; Las Vegas's for T-Mobile Arena and for the Las Vegas Strip Circuit (`circuit`, a district's circuit). A venue of none of these has no area.
46
51
  * @example
47
52
  * ```ts
48
53
  * import { areaKeysFor } from "@johnmorrisdotca/kyogijo-stadiums/areas";
@@ -50,12 +55,16 @@ const LOADERS = {
50
55
  * // [ 'vancouver', 'vancouver-nature' ]
51
56
  * ```
52
57
  */
53
- export function areaKeysFor(venues) {
58
+ export function areaKeysFor(venues, circuit) {
54
59
  const ids = new Set(venues.map((key) => key.split(":")[0]));
55
60
  const has = (list) => list.some((id) => ids.has(id));
56
61
  if (has(TOKYO_VENUES))
57
62
  return ["tokyo-dome-city"];
58
63
  const keys = [];
64
+ // A circuit's surroundings, for the circuit or a venue beside it (T-Mobile Arena, by the Las Vegas Strip Circuit).
65
+ for (const [key, round] of Object.entries(CIRCUIT_AREAS))
66
+ if (has(round.venues) || (circuit !== undefined && round.circuits.includes(circuit)))
67
+ keys.push(key);
59
68
  // Montreal's two venues stand six kilometres apart: together they take the city between them, each alone its own surroundings. New York's and New Jersey's venues are in different boroughs and states: each shown takes its own.
60
69
  const montreal = ["montreal-downtown", "montreal-olympic"].filter((key) => has(CITYCTX_DETAIL[key]));
61
70
  if (montreal.length === 2)
@@ -114,6 +123,6 @@ export function loadArea(key) {
114
123
  * console.log(plan.context?.attribution.en.length);
115
124
  * ```
116
125
  */
117
- export function loadAreasFor(venues) {
118
- return Promise.all(areaKeysFor(venues).map(loadArea));
126
+ export function loadAreasFor(venues, circuit) {
127
+ return Promise.all(areaKeysFor(venues, circuit).map(loadArea));
119
128
  }
@@ -42,6 +42,8 @@ export interface AthleticsPlan {
42
42
  box: MBox;
43
43
  /** Everything drawn: the box and what lies beyond it (the jump strip, the runways' ends, the straight). */
44
44
  extent: MBox;
45
+ /** What `extent` holds as points (the oval and what lies beyond it): what a crowd's stands clear. */
46
+ outline?: [number, number][];
45
47
  areas: Area[];
46
48
  shapes: Shape[];
47
49
  digits: Digits[];
@@ -224,13 +224,28 @@ export function athleticsPlan(o) {
224
224
  const box = { x0: -S / 2 - Rout, y0: -Rout, x1: S / 2 + Rout, y1: Rout };
225
225
  let extent = grow(box, 1);
226
226
  const edgeStraight = R + straight.lanes * straight.lane;
227
+ // What lies beyond the oval: the sprint straight, the jumps' strip, the javelin's runways.
228
+ const beyond = [];
227
229
  if (!indoor)
228
- extent = union([extent, { x0: straight.x0, y0: -Rout, x1: straight.x1, y1: edgeStraight }]);
230
+ beyond.push({ x0: straight.x0, y0: -Rout, x1: straight.x1, y1: edgeStraight });
229
231
  if (events.includes("long-jump") || events.includes("triple-jump"))
230
- extent = union([extent, jumpStrip()]);
231
- if (events.includes("javelin-throw"))
232
- extent = union([extent, ...inf.facilities.filter((f) => f.event === "javelin-throw").map((f) => f.box)].map((b) => ({ x0: Math.max(b.x0, -S / 2 - Rout - 6), y0: b.y0, x1: Math.min(b.x1, S / 2 + Rout + 6), y1: b.y1 })));
232
+ beyond.push(jumpStrip());
233
+ extent = union([extent, ...beyond]);
234
+ if (events.includes("javelin-throw")) {
235
+ const runways = inf.facilities.filter((f) => f.event === "javelin-throw").map((f) => f.box);
236
+ const cut = (b) => ({ x0: Math.max(b.x0, -S / 2 - Rout - 6), y0: b.y0, x1: Math.min(b.x1, S / 2 + Rout + 6), y1: b.y1 });
237
+ extent = union([extent, ...runways].map(cut));
238
+ beyond.push(...runways.map(cut));
239
+ }
233
240
  extent = grow(extent, 0);
241
+ const rim = Rout + 1;
242
+ const outline = [];
243
+ for (let i = 0; i <= 32; i += 1) {
244
+ const a = -Math.PI / 2 + (Math.PI * i) / 32;
245
+ outline.push([S / 2 + rim * Math.cos(a), rim * Math.sin(a)], [-S / 2 - rim * Math.cos(a), rim * Math.sin(a)]);
246
+ }
247
+ for (const b of beyond)
248
+ outline.push([b.x0, b.y0], [b.x1, b.y0], [b.x1, b.y1], [b.x0, b.y1]);
234
249
  const plan = {
235
250
  sport: "athletics",
236
251
  league: league.id,
@@ -243,6 +258,7 @@ export function athleticsPlan(o) {
243
258
  infield,
244
259
  box,
245
260
  extent,
261
+ outline,
246
262
  areas: parts.areas,
247
263
  shapes: parts.shapes,
248
264
  digits: parts.digits,
@@ -3,7 +3,7 @@ export const CATALOGUE_DATA = {
3
3
  "generatedAt": "2026-11-03",
4
4
  "package": {
5
5
  "name": "@johnmorrisdotca/kyogijo-stadiums",
6
- "version": "0.57.0"
6
+ "version": "0.59.0"
7
7
  },
8
8
  "address": "https://johnmorrisdotca.github.io/kyogijo-stadiums/catalogue.json",
9
9
  "site": "https://johnmorrisdotca.github.io/kyogijo-stadiums/",
@@ -3,7 +3,7 @@ export { CIRCUITS, CIRCUIT_ATTRIBUTION, CIRCUIT_IDS, circuitById, circuitPlan }
3
3
  export type { CircuitPlan, PlanTurn } from "./circuits.ts";
4
4
  export { circuitDrawing, circuitTable, circuitText, drawCircuit } from "./circuits.draw.ts";
5
5
  export type { CircuitDrawing } from "./circuits.draw.ts";
6
- export { CIRCUIT_MARKER, CIRCUIT_PLANES_MAX, CIRCUIT_PRESETS, buildCircuitTurn, circuitTurnMarkup, mountCircuitTurn } from "./circuits.turn.ts";
6
+ export { CIRCUIT_MARKER, CIRCUIT_PLANES_MAX, CIRCUIT_PRESETS, buildCircuitTurn, circuitTurnMarkup, circuitUprights, mountCircuitTurn } from "./circuits.turn.ts";
7
7
  export { KYOGIJO_CIRCUIT_STYLE } from "./circuits.style.ts";
8
8
  export { CIRCUIT_WORDS } from "./circuits.words.ts";
9
9
  export type { CircuitWords } from "./circuits.words.ts";
@@ -3,7 +3,7 @@
3
3
  import "./strings.js";
4
4
  export { CIRCUITS, CIRCUIT_ATTRIBUTION, CIRCUIT_IDS, circuitById, circuitPlan } from "./circuits.js";
5
5
  export { circuitDrawing, circuitTable, circuitText, drawCircuit } from "./circuits.draw.js";
6
- export { CIRCUIT_MARKER, CIRCUIT_PLANES_MAX, CIRCUIT_PRESETS, buildCircuitTurn, circuitTurnMarkup, mountCircuitTurn } from "./circuits.turn.js";
6
+ export { CIRCUIT_MARKER, CIRCUIT_PLANES_MAX, CIRCUIT_PRESETS, buildCircuitTurn, circuitTurnMarkup, circuitUprights, mountCircuitTurn } from "./circuits.turn.js";
7
7
  export { KYOGIJO_CIRCUIT_STYLE } from "./circuits.style.js";
8
8
  export { CIRCUIT_WORDS } from "./circuits.words.js";
9
9
  export { lineLength } from "./circuits.geo.js";
@@ -164,7 +164,8 @@ export function circuitDrawing(options) {
164
164
  const { title, description } = circuitText(plan, language);
165
165
  const r = 13 * k;
166
166
  const placed = badgesOf(plan, r, k);
167
- let body = el("rect", { class: "ky-ci-ground", x: viewBox.x, y: viewBox.y, width: viewBox.w, height: viewBox.h });
167
+ const framed = options.frame !== false;
168
+ let body = framed ? el("rect", { class: "ky-ci-ground", x: viewBox.x, y: viewBox.y, width: viewBox.w, height: viewBox.h }) : "";
168
169
  body += el("path", { class: "ky-ci-area", d: path(plan.area, true), "stroke-width": num(1.2 * k) });
169
170
  if (plan.building !== undefined)
170
171
  body += el("path", { class: "ky-ci-building", d: path(plan.building.ring, true), "stroke-width": num(1.2 * k), "data-height": num(plan.building.height / FT) });
@@ -200,9 +201,11 @@ export function circuitDrawing(options) {
200
201
  body += el("text", { class: "ky-ci-name", x: viewBox.x + pad * 0.6, y: viewBox.y + 36 * k, "font-size": num(26 * k) }, esc(circuit.names[language]));
201
202
  body += el("text", { class: "ky-ci-facts", x: viewBox.x + pad * 0.6, y: viewBox.y + 60 * k, "font-size": num(16 * k) }, esc(fill(words.facts, { km: km(circuit.length.value, language), turns: circuit.corners.value })));
202
203
  }
203
- body += northArrow([viewBox.x + viewBox.w - pad * 0.6 - 20 * k, viewBox.y + (label ? 44 * k : pad)], plan.north, 40 * k, words.northArrow);
204
- body += scaleBar([viewBox.x + pad * 0.6, viewBox.y + viewBox.h - 14 * k], viewBox.w, k, words.metres);
205
- body += el("text", { class: "ky-ci-credit", x: viewBox.x + viewBox.w - pad * 0.6, y: viewBox.y + viewBox.h - 14 * k, "font-size": num(11 * k), "text-anchor": "end" }, esc(CIRCUIT_ATTRIBUTION));
204
+ if (framed) {
205
+ body += northArrow([viewBox.x + viewBox.w - pad * 0.6 - 20 * k, viewBox.y + (label ? 44 * k : pad)], plan.north, 40 * k, words.northArrow);
206
+ body += scaleBar([viewBox.x + pad * 0.6, viewBox.y + viewBox.h - 14 * k], viewBox.w, k, words.metres);
207
+ body += el("text", { class: "ky-ci-credit", x: viewBox.x + viewBox.w - pad * 0.6, y: viewBox.y + viewBox.h - 14 * k, "font-size": num(11 * k), "text-anchor": "end" }, esc(CIRCUIT_ATTRIBUTION));
208
+ }
206
209
  const titleId = `${gid}-t`;
207
210
  const descId = `${gid}-d`;
208
211
  const css = options.css === "none" ? "" : `<style>${KYOGIJO_CIRCUIT_STYLE}</style>`;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ // Every circuit, and the race on one, held for a district (`district.circuit.ts`): importing this fills the table a district reads a circuit from. The `district` entry imports it; the lean district reads it with `loadCircuits()`. A bundler keeps it for its
2
+ // effect: package.json lists it in `sideEffects`.
3
+ import { circuitDrawing } from "./circuits.draw.js";
4
+ import { carRadius, carsMarkup } from "./circuits.race.draw.js";
5
+ import { mountRaceIn } from "./circuits.race.dom.js";
6
+ import { raceAt } from "./circuits.race.moment.js";
7
+ import { simulateRace } from "./circuits.race.js";
8
+ import { RACE_WORDS } from "./circuits.race.words.js";
9
+ import { circuitOf, circuitPlan } from "./circuits.js";
10
+ import { circuitUprights } from "./circuits.turn.js";
11
+ import { CIRCUIT_WORDS } from "./circuits.words.js";
12
+ import { holdCircuits } from "./district.circuit.js";
13
+ holdCircuits({ circuitOf, circuitPlan, circuitDrawing, circuitUprights, words: CIRCUIT_WORDS, race: { simulateRace, raceAt, carsMarkup, carRadius, mountRaceIn, words: RACE_WORDS } });
@@ -0,0 +1,15 @@
1
+ /** Every circuit's key and its names in English and Japanese (Wikidata's labels), in the order of `CIRCUITS`. */
2
+ export declare const CIRCUIT_INDEX: Readonly<Record<string, {
3
+ en: string;
4
+ ja: string;
5
+ }>>;
6
+ /**
7
+ * Read the circuits for the lean district (`district-lean`), which carries none until it is asked: after this a district's `circuit` option can name any of them. The `district` entry has them already. Asking again gives the same promise.
8
+ * @example
9
+ * ```ts no-run
10
+ * import { loadCircuits, planDistrict } from "@johnmorrisdotca/kyogijo-stadiums/district-lean";
11
+ * await loadCircuits();
12
+ * planDistrict({ venues: [], circuit: "las-vegas" });
13
+ * ```
14
+ */
15
+ export declare function loadCircuits(): Promise<void>;
@@ -0,0 +1,28 @@
1
+ // The circuits by key and name, without their data: what a page lists to choose from before it reads any circuit (the district's page lists them beside the venues), and `loadCircuits`, which reads them all for a lean district.
2
+ // A test holds this list to `CIRCUITS`.
3
+ /** Every circuit's key and its names in English and Japanese (Wikidata's labels), in the order of `CIRCUITS`. */
4
+ export const CIRCUIT_INDEX = Object.freeze({
5
+ "las-vegas": { en: "Las Vegas Strip Circuit", ja: "ラスベガス・ストリップ・サーキット" },
6
+ });
7
+ let loading;
8
+ /**
9
+ * Read the circuits for the lean district (`district-lean`), which carries none until it is asked: after this a district's `circuit` option can name any of them. The `district` entry has them already. Asking again gives the same promise.
10
+ * @example
11
+ * ```ts no-run
12
+ * import { loadCircuits, planDistrict } from "@johnmorrisdotca/kyogijo-stadiums/district-lean";
13
+ * await loadCircuits();
14
+ * planDistrict({ venues: [], circuit: "las-vegas" });
15
+ * ```
16
+ */
17
+ export function loadCircuits() {
18
+ if (loading === undefined) {
19
+ const read = async () => {
20
+ await import("./circuits.held.js");
21
+ };
22
+ loading = read().catch((error) => {
23
+ loading = undefined;
24
+ throw error;
25
+ });
26
+ }
27
+ return loading;
28
+ }
@@ -1,8 +1,10 @@
1
1
  import { type RaceMoment } from "./circuits.race.moment.ts";
2
2
  import { type CircuitRace, type RaceOptions } from "./circuits.race.ts";
3
3
  import type { CircuitOptions } from "./circuits.types.ts";
4
+ import type { Pt } from "./crowd.ts";
4
5
  import { type KyogijoTurn } from "./turn.dom.ts";
5
- import type { TurnSettings } from "./turn.model.ts";
6
+ import type { TurnModel, TurnSettings } from "./turn.model.ts";
7
+ import type { Language } from "./types.ts";
6
8
  /** The speeds the controls offer: real time, ten times and sixty times (a minute of the race a second). */
7
9
  export declare const RACE_SPEEDS: readonly number[];
8
10
  /** What a race's view is drawn from: the circuit's options and the race's. */
@@ -37,12 +39,26 @@ export interface RaceState {
37
39
  speed: number;
38
40
  motion: "smooth" | "step";
39
41
  }
40
- /** What `mountCircuitRace` gives back. */
41
- export interface KyogijoRace {
42
+ /**
43
+ * Where a race is drawn: the circuit's own drawing and 3D view (`mountCircuitRace`), or another scene the circuit lies in, a district's ground among its venues (`mountDistrictRace`). The cars are placed in the circuit's own plan
44
+ * (`raceAt`); `point` says where such a point is in the scene.
45
+ */
46
+ export interface RaceScene<O, V> {
47
+ /** The scene's flat drawing at a moment, its cars as the `.ky-ci-car` groups of `carMarkup`: the race given when it came from a feed, else the simulation the options make. */
48
+ flat(options: O, t: number, given: CircuitRace | undefined): string;
49
+ /** The options the 3D view is mounted and updated with. */
50
+ view(options: O): V;
51
+ /** The 3D view's model. */
52
+ build(options: V, settings: TurnSettings): TurnModel;
53
+ /** Where a point of the circuit's plan lies in the scene: the same point for the circuit's own drawing. */
54
+ point(options: O): (p: Pt) => Pt;
55
+ }
56
+ /** What `mountCircuitRace` gives back (and `mountDistrictRace`, with a district's options and 3D view). */
57
+ export interface KyogijoRace<O = RaceViewOptions, V = CircuitOptions> {
42
58
  /** The race it plays. */
43
59
  readonly race: CircuitRace;
44
60
  /** The 3D view, when the race is on one. */
45
- readonly turn: KyogijoTurn<CircuitOptions> | undefined;
61
+ readonly turn: KyogijoTurn<V> | undefined;
46
62
  state(): RaceState;
47
63
  /** The race at the clock's time: the standings, the lap, the fastest lap. */
48
64
  moment(): RaceMoment;
@@ -55,7 +71,7 @@ export interface KyogijoRace {
55
71
  nextLap(): void;
56
72
  setSpeed(speed: number): void;
57
73
  /** Draw again with new options (the language, the theme): the clock and the race stay where they are. A new circuit, seed, laps or cars is a new race: mount it again. */
58
- update(options: RaceViewOptions): void;
74
+ update(options: O): void;
59
75
  /** Play another race in the same view (a feed that came in again while a race is followed): the clock stays where it is, held to the new race's end, and stays playing or paused. */
60
76
  replace(race: CircuitRace): void;
61
77
  destroy(): void;
@@ -71,3 +87,7 @@ export interface KyogijoRace {
71
87
  * ```
72
88
  */
73
89
  export declare function mountCircuitRace(host: HTMLElement, initial: RaceViewOptions, settings?: RaceViewSettings): KyogijoRace;
90
+ /** A race in a page, drawn in a scene: what `mountCircuitRace` does on the circuit's own drawing, and `mountDistrictRace` on a district's ground. */
91
+ export declare function mountRaceIn<O extends RaceOptions & {
92
+ language?: Language;
93
+ }, V>(host: HTMLElement, initial: O, scene: RaceScene<O, V>, settings?: RaceViewSettings): KyogijoRace<O, V>;
@@ -11,6 +11,13 @@ import { fill } from "./strings.core.js";
11
11
  import { mountTurn } from "./turn.dom.js";
12
12
  /** The speeds the controls offer: real time, ten times and sixty times (a minute of the race a second). */
13
13
  export const RACE_SPEEDS = Object.freeze([1, 10, 60]);
14
+ /** The circuit's own scene. */
15
+ const CIRCUIT_SCENE = {
16
+ flat: (options, t, given) => raceDrawing({ ...options, t }, given).svg,
17
+ view: (options) => options,
18
+ build: buildCircuitTurn,
19
+ point: () => (p) => p,
20
+ };
14
21
  const ICONS = { play: "M8 5v14l11-7z", pause: "M6 5h4v14H6zM14 5h4v14h-4z", next: "M6 5l8.5 7L6 19zM16 5h2v14h-2z" };
15
22
  const icon = (d) => `<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="${d}"/></svg>`;
16
23
  /** The plane of a car standing in a 3D view: a disc that faces the eye, its lower edge on the floor over its place. */
@@ -26,8 +33,13 @@ const carPlane = (id, x, y, d, svg) => ({ id: `car-${id}`, kind: "player", o: [x
26
33
  * ```
27
34
  */
28
35
  export function mountCircuitRace(host, initial, settings = {}) {
36
+ return mountRaceIn(host, initial, CIRCUIT_SCENE, settings);
37
+ }
38
+ /** A race in a page, drawn in a scene: what `mountCircuitRace` does on the circuit's own drawing, and `mountDistrictRace` on a district's ground. */
39
+ export function mountRaceIn(host, initial, scene, settings = {}) {
29
40
  const doc = host.ownerDocument;
30
41
  let options = { ...initial };
42
+ let place = scene.point(options);
31
43
  let language = options.language === "ja" ? "ja" : "en";
32
44
  let race = settings.race ?? simulateRace(options);
33
45
  let fromFeed = settings.race !== undefined;
@@ -101,7 +113,7 @@ export function mountCircuitRace(host, initial, settings = {}) {
101
113
  }
102
114
  };
103
115
  const drawFlat = () => {
104
- stage.innerHTML = raceDrawing({ ...options, t: state.t }, fromFeed ? race : undefined).svg;
116
+ stage.innerHTML = scene.flat(options, state.t, fromFeed ? race : undefined);
105
117
  bindFlat();
106
118
  };
107
119
  /** The model of the 3D view with a disc for each car at the clock's time. */
@@ -113,7 +125,8 @@ export function mountCircuitRace(host, initial, settings = {}) {
113
125
  planes.clear();
114
126
  for (const line of [...moment.standings].sort((a, b) => a.index - b.index)) {
115
127
  const svg = carMarkup({ ...line, at: [r, r] }, r * 0.9, language).replace(/ role="img"[^>]*?aria-label="[^"]*"/, "");
116
- planes.set(line.car.id, carPlane(line.car.id, line.at[0], line.at[1], disc, svg));
128
+ const at = place(line.at);
129
+ planes.set(line.car.id, carPlane(line.car.id, at[0], at[1], disc, svg));
117
130
  }
118
131
  return { ...model, planes: [...model.planes, ...planes.values()] };
119
132
  };
@@ -130,7 +143,7 @@ export function mountCircuitRace(host, initial, settings = {}) {
130
143
  if (settings.view === "3d") {
131
144
  const holder = doc.createElement("div");
132
145
  stage.replaceChildren(holder);
133
- turn = mountTurn(holder, options, settings.turn ?? {}, buildCircuitTurn, false, (lent) => {
146
+ turn = mountTurn(holder, scene.view(options), settings.turn ?? {}, scene.build, false, (lent) => {
134
147
  inner = lent;
135
148
  });
136
149
  if (inner !== undefined) {
@@ -209,15 +222,16 @@ export function mountCircuitRace(host, initial, settings = {}) {
209
222
  const car = cars.get(line.car.id);
210
223
  if (car === undefined)
211
224
  continue;
225
+ const p = place(line.at);
212
226
  if (inner !== undefined) {
213
227
  const plane = planes.get(line.car.id);
214
- if (plane !== undefined && (plane.o[0] !== line.at[0] - disc / 2 || plane.o[1] !== line.at[1])) {
215
- plane.o = [line.at[0] - disc / 2, line.at[1], disc];
228
+ if (plane !== undefined && (plane.o[0] !== p[0] - disc / 2 || plane.o[1] !== p[1])) {
229
+ plane.o = [p[0] - disc / 2, p[1], disc];
216
230
  moved = true;
217
231
  }
218
232
  }
219
233
  else {
220
- const at = `translate(${num(line.at[0])} ${num(line.at[1])})`;
234
+ const at = `translate(${num(p[0])} ${num(p[1])})`;
221
235
  if (car.at !== at) {
222
236
  car.at = at;
223
237
  car.el?.setAttribute("transform", at);
@@ -375,8 +389,9 @@ export function mountCircuitRace(host, initial, settings = {}) {
375
389
  update(next) {
376
390
  options = { ...next, circuit: options.circuit, seed: options.seed, laps: options.laps, cars: options.cars };
377
391
  language = options.language === "ja" ? "ja" : "en";
392
+ place = scene.point(options);
378
393
  if (turn !== undefined)
379
- turn.update(options);
394
+ turn.update(scene.view(options));
380
395
  else
381
396
  drawFlat();
382
397
  buildBoard();
@@ -55,5 +55,5 @@ export declare function drawRace(options: RaceDrawOptions): string;
55
55
  * ```
56
56
  */
57
57
  export declare function raceBoard(moment: RaceMoment, language?: Language): string;
58
- /** The rules of a race: the cars on the drawing, the clock, the controls and the board. Scoped to `.ky-race` and the drawing's cars, in the page's own colours. */
58
+ /** The rules of a race: the cars on the drawing, the clock, the controls and the board. Scoped to `.ky-race` and the cars of a circuit's or a district's drawing, in the page's own colours. */
59
59
  export declare const RACE_STYLE: string;
@@ -123,11 +123,11 @@ export function raceBoard(moment, language = "en") {
123
123
  });
124
124
  return `<table class="ky-race-board"><caption>${esc(w.caption)}</caption>${head}<tbody>${rows.join("")}</tbody></table>`;
125
125
  }
126
- /** The rules of a race: the cars on the drawing, the clock, the controls and the board. Scoped to `.ky-race` and the drawing's cars, in the page's own colours. */
126
+ /** The rules of a race: the cars on the drawing, the clock, the controls and the board. Scoped to `.ky-race` and the cars of a circuit's or a district's drawing, in the page's own colours. */
127
127
  export const RACE_STYLE = `
128
- .ky-circuit .ky-ci-car-dot,.ky-turn .ky-ci-car-dot{stroke:var(--ky-ci-ground,#fff)}
129
- .ky-circuit .ky-ci-car-n,.ky-turn .ky-ci-car-n{font-family:var(--ky-font,system-ui,sans-serif);font-weight:800;text-anchor:middle;font-variant-numeric:tabular-nums}
130
- .ky-circuit .ky-ci-car[data-status=out]{opacity:.45}
128
+ .ky-circuit .ky-ci-car-dot,.ky-district .ky-ci-car-dot,.ky-turn .ky-ci-car-dot{stroke:var(--ky-ci-ground,#fff)}
129
+ .ky-circuit .ky-ci-car-n,.ky-district .ky-ci-car-n,.ky-turn .ky-ci-car-n{font-family:var(--ky-font,system-ui,sans-serif);font-weight:800;text-anchor:middle;font-variant-numeric:tabular-nums}
130
+ .ky-circuit .ky-ci-car[data-status=out],.ky-district .ky-ci-car[data-status=out]{opacity:.45}
131
131
  .ky-race{display:block;color:inherit}
132
132
  .ky-race .ky-race-bar{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;margin-top:.5rem}
133
133
  .ky-race .ky-race-bar button{box-sizing:border-box;min-width:44px;min-height:44px;margin:0;padding:0 .7rem;border:1px solid currentColor;border-radius:8px;background:transparent;color:inherit;font:inherit;font-weight:700;cursor:pointer}
@@ -1,6 +1,8 @@
1
+ import { type CircuitDrawing } from "./circuits.draw.ts";
1
2
  import type { CircuitOptions } from "./circuits.types.ts";
2
3
  import { type PresetTable } from "./turn.camera.ts";
3
4
  import { type KyogijoTurn } from "./turn.dom.ts";
5
+ import { type Upright } from "./turn.kit.ts";
4
6
  import { type TurnModel, type TurnSettings } from "./turn.model.ts";
5
7
  /**
6
8
  * The four viewpoints of a circuit: straight down, the main camera high over the long side of the lap, the end-on view along it and the low corner. A lap is kilometres across and flat, so the eye is kept higher than
@@ -14,6 +16,12 @@ export declare const CIRCUIT_MARKER: Readonly<{
14
16
  }>;
15
17
  /** The most planes a circuit's 3D view may have: its markers (three planes each), its pit building and nothing else. A test holds every circuit to it. */
16
18
  export declare const CIRCUIT_PLANES_MAX = 120;
19
+ /**
20
+ * What stands up on a circuit's floor, in its plan's frame (feet): the pit building at its mapped height and each turn's number on a post over its badge's place beside the bend, the marker turned to face the eye and leaning
21
+ * back to be read square on. `drawn` is the circuit's drawing (its badges are where the markers stand) and `thin` the width of a post. The 3D view of the circuit stands these, and a district with the circuit stands them where
22
+ * it puts the circuit.
23
+ */
24
+ export declare function circuitUprights(drawn: CircuitDrawing, thin: number): Upright[];
17
25
  /**
18
26
  * A circuit as a 3D model: its drawing as the floor, the pit building standing on it at its mapped height, and a marker with each turn's number on a post over its bend. `settings.uprights: false` is the floor alone.
19
27
  * A `TurnModel` like any other: `turnMarkupOf` and `mountCircuitTurn` make a view of it.
@@ -23,6 +23,34 @@ export const CIRCUIT_PRESETS = { overhead: TURN_PRESETS.overhead, broadcast: { y
23
23
  export const CIRCUIT_MARKER = Object.freeze({ size: 30, post: 22 });
24
24
  /** The most planes a circuit's 3D view may have: its markers (three planes each), its pit building and nothing else. A test holds every circuit to it. */
25
25
  export const CIRCUIT_PLANES_MAX = 120;
26
+ /**
27
+ * What stands up on a circuit's floor, in its plan's frame (feet): the pit building at its mapped height and each turn's number on a post over its badge's place beside the bend, the marker turned to face the eye and leaning
28
+ * back to be read square on. `drawn` is the circuit's drawing (its badges are where the markers stand) and `thin` the width of a post. The 3D view of the circuit stands these, and a district with the circuit stands them where
29
+ * it puts the circuit.
30
+ */
31
+ export function circuitUprights(drawn, thin) {
32
+ const plan = drawn.plan;
33
+ const k = drawn.k;
34
+ const items = [];
35
+ if (plan.building !== undefined) {
36
+ const planes = blockPlanes(shellBlock("pit", plan.building.ring, plan.building.height, undefined), "pit-");
37
+ const [cx, cy] = plan.building.ring.reduce((s, p) => [s[0] + p[0] / plan.building.ring.length, s[1] + p[1] / plan.building.ring.length], [0, 0]);
38
+ items.push({ id: "pit-building", kind: "blocks", at: [cx, cy], height: plan.building.height, planes });
39
+ }
40
+ const size = CIRCUIT_MARKER.size * k;
41
+ const top = CIRCUIT_MARKER.post * k;
42
+ const marks = [];
43
+ for (const b of drawn.badges) {
44
+ const [x, y] = b.at;
45
+ const r = size / 2;
46
+ const svg = `<circle class="u-ci-badge" cx="${num(r)}" cy="${num(r)}" r="${num(r * 0.94)}" stroke-width="${num(r * 0.1)}"/><text class="u-ci-badge-n" x="${num(r)}" y="${num(r + r * 0.4)}" font-size="${num(r * (b.n >= 10 ? 1.05 : 1.25))}">${b.n}</text>`;
47
+ marks.push({ ...plane(`turn-${b.n}`, "sign", [x - r, y, top + size], [size, 0, 0], [0, 0, -size], svg), billboard: true, lean: true });
48
+ marks.push(...pole(`turn-${b.n}-post`, "pole", x, y, 0, top, thin, "u-pole"));
49
+ }
50
+ if (marks.length > 0)
51
+ items.push({ id: "turns", kind: "flags", at: [(plan.box.x0 + plan.box.x1) / 2, (plan.box.y0 + plan.box.y1) / 2], height: top + size, planes: marks });
52
+ return items;
53
+ }
26
54
  /**
27
55
  * A circuit as a 3D model: its drawing as the floor, the pit building standing on it at its mapped height, and a marker with each turn's number on a post over its bend. `settings.uprights: false` is the floor alone.
28
56
  * A `TurnModel` like any other: `turnMarkupOf` and `mountCircuitTurn` make a view of it.
@@ -37,27 +65,7 @@ export function buildCircuitTurn(options, settings = {}) {
37
65
  const drawn = circuitDrawing({ ...options, id: `${id}-floor`, label: false, turns: !standUp });
38
66
  const plan = drawn.plan;
39
67
  const { x: vx, y: vy, w: vw, h: vh } = drawn.viewBox;
40
- const k = drawn.k;
41
- const items = [];
42
- if (standUp && plan.building !== undefined) {
43
- const planes = blockPlanes(shellBlock("pit", plan.building.ring, plan.building.height, undefined), "pit-");
44
- const [cx, cy] = plan.building.ring.reduce((s, p) => [s[0] + p[0] / plan.building.ring.length, s[1] + p[1] / plan.building.ring.length], [0, 0]);
45
- items.push({ id: "pit-building", kind: "blocks", at: [cx, cy], height: plan.building.height, planes });
46
- }
47
- if (standUp) {
48
- // Each turn's number on a post, over the badge's place beside the bend; the marker turns to face the eye and leans back to be read square on, and lies flat seen from above.
49
- const size = CIRCUIT_MARKER.size * k;
50
- const top = CIRCUIT_MARKER.post * k;
51
- const marks = [];
52
- for (const b of drawn.badges) {
53
- const [x, y] = b.at;
54
- const r = size / 2;
55
- const svg = `<circle class="u-ci-badge" cx="${num(r)}" cy="${num(r)}" r="${num(r * 0.94)}" stroke-width="${num(r * 0.1)}"/><text class="u-ci-badge-n" x="${num(r)}" y="${num(r + r * 0.4)}" font-size="${num(r * (b.n >= 10 ? 1.05 : 1.25))}">${b.n}</text>`;
56
- marks.push({ ...plane(`turn-${b.n}`, "sign", [x - r, y, top + size], [size, 0, 0], [0, 0, -size], svg), billboard: true, lean: true });
57
- marks.push(...pole(`turn-${b.n}-post`, "pole", x, y, 0, top, thinnest(vw, vh), "u-pole"));
58
- }
59
- items.push({ id: "turns", kind: "flags", at: [(plan.box.x0 + plan.box.x1) / 2, (plan.box.y0 + plan.box.y1) / 2], height: top + size, planes: marks });
60
- }
68
+ const items = standUp ? circuitUprights(drawn, thinnest(vw, vh)) : [];
61
69
  const area = plan.box;
62
70
  const uprights = { items, lift: 0, thin: thinnest(vw, vh), area };
63
71
  const planes = planesOf(uprights);
@@ -112,6 +112,8 @@ export interface CircuitOptions {
112
112
  turns?: boolean;
113
113
  /** The arrows that show which way the lap runs: `true` (the default) or `false`. */
114
114
  arrows?: boolean;
115
+ /** `false` leaves out the drawing's frame (its ground, the north arrow, the scale bar and the credit): for a drawing laid inside another that has its own (a district). `true` when missing. */
116
+ frame?: boolean;
115
117
  /** The prefix of the ids inside the drawing, for a page with more than one; a hash of the options when missing. */
116
118
  id?: string;
117
119
  /** More classes on the root. */