@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/dist/context.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type LonLat } from "./venues.geo.ts";
2
2
  import { type Plane, type Upright } from "./turn.kit.ts";
3
3
  import type { Pt } from "./crowd.ts";
4
+ import { type ContextLandmark, type PlacedLandmark } from "./landmarks.ts";
4
5
  /** One layer of an area's data: its rows, and where they came from and under what terms, since a layer has a source of its own (buildings from PLATEAU; trees and land cover from another). */
5
6
  export interface ContextLayer {
6
7
  /** What it was made from: the dataset and its page. */
@@ -44,6 +45,11 @@ export interface ContextArea {
44
45
  trees?: ContextLayer;
45
46
  landCover?: ContextLayer;
46
47
  };
48
+ /** The most of this area's buildings the 3D view raises, when that is fewer than the tier's count (`CONTEXT_BLOCKS` at its best): a city of big, many-sided buildings (the casinos of the Las Vegas Strip) raises fewer, to stay within the
49
+ * district's budget of planes. Missing: the tier's count. */
50
+ blocks?: number;
51
+ /** The area's famous landmarks, each drawn by a shape of its own and named (`landmarks.ts`): the Sphere, the High Roller and the Fountains of Bellagio round the Las Vegas Strip Circuit. Missing: none. */
52
+ landmarks?: readonly ContextLandmark[];
47
53
  }
48
54
  /** One building as the district holds it: its outline in feet in the district's frame (x to the right, y down the page), its height and the rise of its roof in feet. */
49
55
  export interface ContextBlock {
@@ -91,6 +97,10 @@ export interface ContextPlan {
91
97
  trees?: ContextTree[];
92
98
  /** The buildings of the model that are a venue (the Dome's own parts), left out: the venue draws them. */
93
99
  owned: number;
100
+ /** The landmarks of the areas in the frame, each with its own shape; missing when the areas have none. */
101
+ landmarks?: PlacedLandmark[];
102
+ /** The most buildings the areas let the 3D view raise (the least of their `blocks`); missing when none sets it. */
103
+ most?: number;
94
104
  /** The credits of the areas used, in each language. */
95
105
  attribution: {
96
106
  en: string[];
@@ -172,6 +182,6 @@ export declare function slabPlanes(ring: readonly Pt[], level: number, thickness
172
182
  side: string;
173
183
  }, look: "chosen" | "faint" | "plain"): Plane[];
174
184
  /** The blocks of the 3D view: the `most` buildings that matter most (the tallest and widest, by height times the side of their ground), each a few planes, in the order the plan lists them. */
175
- export declare function contextUprights(plan: ContextPlan, most?: number): Upright[];
185
+ export declare function contextUprights(plan: ContextPlan, asked?: number): Upright[];
176
186
  /** The building a raised venue stands in as a block: its outline in feet, its height, `xray` the middle of what is seen through its walls (none for a solid one). */
177
187
  export declare function shellBlock(key: string, ring: readonly Pt[], height: number, xray: Pt | undefined): ContextBlock;
package/dist/context.js CHANGED
@@ -6,6 +6,7 @@ import { FEET_PER_METRE } from "./geometry.js";
6
6
  import { boxOf, hullOf, inside, metresFrom } from "./venues.geo.js";
7
7
  import { n, shy, wall } from "./turn.kit.js";
8
8
  import { pointList, raked } from "./venue.geom.js";
9
+ import { landmarksFloor, ownedByLandmark, placeLandmarks, underLandmark } from "./landmarks.js";
9
10
  /** How many buildings are raised as blocks in the 3D view (the biggest and the tallest of those on the plan): the rest are the floor's own, flat. Every block is a few elements of the page. */
10
11
  export const CONTEXT_BLOCKS = 130;
11
12
  /** Feet of the plan's frame beyond the venues' buildings that a district with context shows, so that the streets round them are in it: 150 metres. */
@@ -79,6 +80,7 @@ export function placeContext(areas, origin, turn, frame, venues) {
79
80
  let owned = 0;
80
81
  const hulls = venues.map((v) => hullOf([...v.footprint]));
81
82
  const [fx0, fy0, fx1, fy1] = [frame.x, frame.y, frame.x + frame.w, frame.y + frame.h];
83
+ const landmarks = placeLandmarks(areas, origin, turn, frame);
82
84
  for (const area of areas) {
83
85
  const [east, north] = metresFrom(origin, [area.origin])[0];
84
86
  (area.layers.buildings?.rows ?? []).forEach((row, index) => {
@@ -96,6 +98,11 @@ export function placeContext(areas, origin, turn, frame, venues) {
96
98
  owned += 1;
97
99
  return;
98
100
  }
101
+ // A building that is a landmark's own (the Sphere's, drawn as a dome) is left out too.
102
+ if (ownedByLandmark(landmarks, ring)) {
103
+ owned += 1;
104
+ return;
105
+ }
99
106
  blocks.push({ id: `${area.key}-${index}`, ring, height: b.height * FEET_PER_METRE, rise: b.rise * FEET_PER_METRE, area: Math.abs(signedArea(ring)), ghost: holds, whole: box.x0 >= fx0 && box.x1 <= fx1 && box.y0 >= fy0 && box.y1 <= fy1 });
100
107
  });
101
108
  }
@@ -147,7 +154,8 @@ export function placeContext(areas, origin, turn, frame, venues) {
147
154
  // An empty credit (a layer whose credit another layer of the area has said) adds nothing.
148
155
  const layers = areas.flatMap((a) => Object.values(a.layers));
149
156
  const credits = (language) => [...new Set(layers.map((l) => l.attribution[language]).filter((text) => text !== ""))];
150
- return { blocks, streets, land, trees, owned, attribution: { en: credits("en"), ja: credits("ja") } };
157
+ const most = areas.reduce((least, a) => (a.blocks === undefined ? least : Math.min(least, a.blocks)), Infinity);
158
+ return { blocks, streets, land, trees, owned, ...(landmarks.length === 0 ? {} : { landmarks }), ...(Number.isFinite(most) ? { most } : {}), attribution: { en: credits("en"), ja: credits("ja") } };
151
159
  }
152
160
  /** A share of the trees, spread evenly over the lot: every tree at 1, none at 0, and at 0.5 every other. The same trees every time, so that a view drawn again at a tier keeps the ones it kept. */
153
161
  export function thinTrees(trees, share) {
@@ -209,12 +217,12 @@ export function treesFloor(trees) {
209
217
  export function contextFloor(plan) {
210
218
  const under = `${landFloor(plan.land ?? [])}${streetsFloor(plan.streets ?? [])}${treesFloor(plan.trees ?? [])}`;
211
219
  if (plan.blocks.length === 0)
212
- return under;
220
+ return `${under}${landmarksFloor(plan.landmarks ?? [])}`;
213
221
  const paths = [[], [], [], [], []];
214
222
  for (const b of plan.blocks)
215
223
  paths[b.ghost ? 4 : toneOf(b.height)].push(`M${b.ring.map(([x, y]) => `${num(x)} ${num(y)}`).join("L")}Z`);
216
224
  const classes = ["ky-dist-b0", "ky-dist-b1", "ky-dist-b2", "ky-dist-b3", "ky-dist-ghost"];
217
- return `${under}<g class="ky-dist-context" aria-hidden="true">${paths.map((d, i) => (d.length === 0 ? "" : `<path class="ky-dist-block ${classes[i]}" d="${d.join("")}"/>`)).join("")}</g>`;
225
+ return `${under}<g class="ky-dist-context" aria-hidden="true">${paths.map((d, i) => (d.length === 0 ? "" : `<path class="ky-dist-block ${classes[i]}" d="${d.join("")}"/>`)).join("")}</g>${landmarksFloor(plan.landmarks ?? [])}`;
218
226
  }
219
227
  // ---- The blocks of the 3D view. ----
220
228
  /** How much a building matters to the view: tall and wide first. */
@@ -368,8 +376,14 @@ export function slabPlanes(ring, level, thickness, ids, look) {
368
376
  return planes;
369
377
  }
370
378
  /** The blocks of the 3D view: the `most` buildings that matter most (the tallest and widest, by height times the side of their ground), each a few planes, in the order the plan lists them. */
371
- export function contextUprights(plan, most = CONTEXT_BLOCKS) {
379
+ export function contextUprights(plan, asked = CONTEXT_BLOCKS) {
380
+ const most = Math.min(asked, plan.most ?? Infinity);
372
381
  const wanted = new Set(plan.blocks.filter((b) => b.whole).sort((a, b) => weight(b) - weight(a) || (a.id < b.id ? -1 : 1)).slice(0, Math.max(0, most)).map((b) => b.id));
382
+ // The buildings of a named landmark stand whatever the count (at a tier that raises any).
383
+ if (most > 0)
384
+ for (const b of plan.blocks)
385
+ if (b.whole && underLandmark(plan.landmarks ?? [], b.ring))
386
+ wanted.add(b.id);
373
387
  const items = [];
374
388
  for (const b of plan.blocks) {
375
389
  if (!wanted.has(b.id))
package/dist/crowd.d.ts CHANGED
@@ -70,6 +70,14 @@ export interface CrowdStands {
70
70
  stadium?: "purpose" | "nfl" | "cfl";
71
71
  /** The playing surface, in the wide frame. */
72
72
  box: Box;
73
+ /**
74
+ * The floor round the playing surface, where the surface draws more than its box (a court's run-off, a track's sprint straight, runways and pits): its bounding box and its outline as points, in the wide frame.
75
+ * The first row of seats clears all of it by `FLOOR_CLEAR`, and the opening is centred on its box. Missing for a real venue's stands and a ballpark's, which are laid out from their own data.
76
+ */
77
+ floor?: {
78
+ box: Box;
79
+ points: Pt[];
80
+ };
73
81
  rings: StandRing[];
74
82
  /** Boxes where the stands are left out (the open end of a horseshoe, the ends of two sides). */
75
83
  covers: [x0: number, y0: number, x1: number, y1: number][];
@@ -310,10 +318,20 @@ export interface LayoutInput {
310
318
  };
311
319
  /** A real venue's stands in place of the sport's usual tiers (see `venueBowl`). */
312
320
  real?: RealBowl;
321
+ /**
322
+ * Everything the surface draws round its playing area, in the wide frame: the bounding box (the surface's own view with no margin) and, where the floor is not that rectangle (an athletics track, an oval
323
+ * ground), its outline as points. The opening of the stands is centred on it and clears it. Without it the floor is the box.
324
+ */
325
+ floor?: {
326
+ box: Box;
327
+ points?: readonly (readonly [number, number])[];
328
+ };
313
329
  /** The ballpark's fence as points on the field, for a ballpark (see `parkLayout`). */
314
330
  fence?: readonly Pt[];
315
331
  /** The foul lines' length at the poles and the backstop's distance, for a ballpark. */
316
332
  backstop?: number;
317
333
  }
334
+ /** The room kept between the floor (everything the surface draws round its playing area) and the first row of seats, in feet. */
335
+ export declare const FLOOR_CLEAR = 2;
318
336
  /** The layout of a sport's stands round its surface: rings, covers, sectors and gaps. For a ballpark see `parkLayout`. */
319
337
  export declare function layoutOf(input: LayoutInput): CrowdStands;
package/dist/crowd.js CHANGED
@@ -330,6 +330,62 @@ function offsets(p, box, stadium) {
330
330
  const side = g.side + (f.width - box.h) / 2;
331
331
  return { left: end, right: end, top: side, bottom: side + g.bench };
332
332
  }
333
+ /** The room kept between the floor (everything the surface draws round its playing area) and the first row of seats, in feet. */
334
+ export const FLOOR_CLEAR = 2;
335
+ /** A point inside a rounded rectangle or an ellipse of half sizes `hx`, `hy` about the origin, with `clear` to spare. */
336
+ function insideOpening(ax, ay, hx, hy, r, oval, clear) {
337
+ const [w, h] = [hx - clear, hy - clear];
338
+ if (w <= 0 || h <= 0)
339
+ return false;
340
+ if (oval)
341
+ return sq(ax / w) + sq(ay / h) <= 1 + 1e-9;
342
+ const rr = Math.max(0, Math.min(r, hx, hy) - clear);
343
+ if (ax > w + 1e-9 || ay > h + 1e-9)
344
+ return false;
345
+ const kx = ax - (w - rr);
346
+ const ky = ay - (h - rr);
347
+ return kx <= 0 || ky <= 0 || sq(kx) + sq(ky) <= sq(rr) + 1e-9;
348
+ }
349
+ /** The first row's opening round a floor, centred on it (the bench room shared by both sides): the sport's distances, or the floor's own shape where they do not clear it, grown alike until its whole outline clears by `FLOOR_CLEAR`. */
350
+ function openingOf(input) {
351
+ const { off, corner, oval, box } = input;
352
+ const floor = input.floor?.box ?? box;
353
+ const cx = floor.x + floor.w / 2;
354
+ const cy = floor.y + floor.h / 2;
355
+ let hx = Math.max(cx - box.x, box.x + box.w - cx) + (off.left + off.right) / 2;
356
+ let hy = Math.max(cy - box.y, box.y + box.h - cy) + (off.top + off.bottom) / 2;
357
+ // A floor the sport's distances do not clear (table tennis): the floor's own shape.
358
+ if (Math.min(hx - floor.w / 2, hy - floor.h / 2) < FLOOR_CLEAR) {
359
+ hx = floor.w / 2 + FLOOR_CLEAR;
360
+ hy = floor.h / 2 + FLOOR_CLEAR;
361
+ }
362
+ const given = input.floor?.points;
363
+ const points = given !== undefined && given.length > 0
364
+ ? given.map(([x, y]) => [x, y])
365
+ : oval
366
+ ? Array.from({ length: 64 }, (_, i) => [cx + (floor.w / 2) * Math.cos((i * Math.PI) / 32), cy + (floor.h / 2) * Math.sin((i * Math.PI) / 32)])
367
+ : [[floor.x, floor.y], [floor.x + floor.w, floor.y], [floor.x + floor.w, floor.y + floor.h], [floor.x, floor.y + floor.h]];
368
+ // A sampled curve: a little to spare.
369
+ const clear = FLOOR_CLEAR + (given !== undefined || oval ? 0.5 : 0);
370
+ const fits = (d) => points.every(([x, y]) => insideOpening(Math.abs(x - cx), Math.abs(y - cy), hx + d, hy + d, corner, oval, clear));
371
+ if (!fits(0)) {
372
+ let lo = 0;
373
+ let hi = 4;
374
+ while (!fits(hi) && hi < 4096)
375
+ hi *= 2;
376
+ for (let i = 0; i < 40; i += 1) {
377
+ const mid = (lo + hi) / 2;
378
+ if (fits(mid))
379
+ hi = mid;
380
+ else
381
+ lo = mid;
382
+ }
383
+ hx += hi;
384
+ hy += hi;
385
+ }
386
+ const round = (v) => Math.round(v * 1e6) / 1e6;
387
+ return { off: { left: round(box.x - (cx - hx)), right: round(cx + hx - box.x - box.w), top: round(box.y - (cy - hy)), bottom: round(cy + hy - box.y - box.h) }, cx, cy, floor, points };
388
+ }
333
389
  /** The layout of a sport's stands round its surface: rings, covers, sectors and gaps. For a ballpark see `parkLayout`. */
334
390
  export function layoutOf(input) {
335
391
  const { sport, box } = input;
@@ -342,15 +398,16 @@ export function layoutOf(input) {
342
398
  const stadium = sport === "soccer" && input.real === undefined ? host ?? "purpose" : undefined;
343
399
  const p = stadium === "nfl" || stadium === "cfl" ? { ...PROFILES[stadium], shape: base.shape, away: base.away } : base;
344
400
  const shape = input.real !== undefined ? "bowl" : crowd.shape ?? p.shape;
345
- const off = input.real?.off ?? offsets(base, box, stadium ?? "purpose");
346
- const cx = box.x + box.w / 2;
347
- const cy = box.y + box.h / 2;
401
+ const fit = input.real === undefined ? openingOf({ off: offsets(base, box, stadium ?? "purpose"), corner: p.corner, oval: base.oval === true, box, floor: input.floor }) : undefined;
402
+ const off = input.real?.off ?? fit?.off ?? offsets(base, box, stadium ?? "purpose");
403
+ const cx = fit?.cx ?? box.x + box.w / 2;
404
+ const cy = fit?.cy ?? box.y + box.h / 2;
348
405
  const rings = input.real === undefined ? [] : input.real.rings.map((ring) => ({ ...ring }));
349
406
  const reachOf = input.real === undefined ? { left: 0, right: 0, top: 0, bottom: 0 } : { ...input.real.reach };
350
407
  for (const t of input.real === undefined ? p.tiers : []) {
351
408
  const at = (k) => {
352
409
  if (base.oval === true)
353
- return { kind: "ellipse", cx, cy, rx: box.w / 2 + (off.left + off.right) / 2 + k, ry: box.h / 2 + (off.top + off.bottom) / 2 + k };
410
+ return { kind: "ellipse", cx, cy, rx: (box.w + off.left + off.right) / 2 + k, ry: (box.h + off.top + off.bottom) / 2 + k };
354
411
  return { kind: "rrect", x0: box.x - off.left - k, y0: box.y - off.top - k, x1: box.x + box.w + off.right + k, y1: box.y + box.h + off.bottom + k, r: p.corner + k };
355
412
  };
356
413
  const region = at(t.to);
@@ -379,17 +436,18 @@ export function layoutOf(input) {
379
436
  const covers = [];
380
437
  const x0 = box.x - off.left;
381
438
  const x1 = box.x + box.w + off.right;
382
- const yTop = last === undefined ? box.y : box.y - reachOf.top - 4;
383
- const yBot = last === undefined ? box.y + box.h : box.y + box.h + reachOf.bottom + 4;
439
+ // The covers reach past the roof's shade (9 feet beyond the upper tier) too.
440
+ const yTop = last === undefined ? box.y : box.y - reachOf.top - 10;
441
+ const yBot = last === undefined ? box.y + box.h : box.y + box.h + reachOf.bottom + 10;
384
442
  if (shape === "horseshoe") {
385
443
  // The open end: everything beyond the surface's end line and the gap that is kept clear.
386
444
  if (crowd.open === "left")
387
- covers.push([box.x - reachOf.left - 4, yTop, x0 + p.corner * 0.5, yBot]);
445
+ covers.push([box.x - reachOf.left - 10, yTop, x0 + p.corner * 0.5, yBot]);
388
446
  else
389
- covers.push([x1 - p.corner * 0.5, yTop, box.x + box.w + reachOf.right + 4, yBot]);
447
+ covers.push([x1 - p.corner * 0.5, yTop, box.x + box.w + reachOf.right + 10, yBot]);
390
448
  }
391
449
  else if (shape === "sides") {
392
- covers.push([box.x - reachOf.left - 4, yTop, x0 + p.corner * 0.5, yBot], [x1 - p.corner * 0.5, yTop, box.x + box.w + reachOf.right + 4, yBot]);
450
+ covers.push([box.x - reachOf.left - 10, yTop, x0 + p.corner * 0.5, yBot], [x1 - p.corner * 0.5, yTop, box.x + box.w + reachOf.right + 10, yBot]);
393
451
  }
394
452
  const concourses = [];
395
453
  rings.forEach((ring, i) => {
@@ -398,6 +456,8 @@ export function layoutOf(input) {
398
456
  concourses.push({ region: ring.hole, hole: before.region });
399
457
  });
400
458
  const stands = { sport, shape, box, rings, covers, concourses, sectors: [], gaps: [], reach: reachOf, seats: rings.reduce((n, r) => n + r.seats, 0) };
459
+ if (fit !== undefined)
460
+ stands.floor = { box: fit.floor, points: fit.points };
401
461
  if (input.real !== undefined)
402
462
  stands.real = input.real.shell;
403
463
  if (input.real?.closedSectors !== undefined)
@@ -490,21 +550,31 @@ export function layoutOf(input) {
490
550
  }
491
551
  }
492
552
  if (crowd.gaps && shape !== "grandstand") {
493
- const bottom = box.y + box.h;
494
- const mid = box.x + box.w / 2;
553
+ // Nothing on the floor: the benches stand between the floor and the first row.
554
+ const edge = box.y + box.h + off.bottom;
555
+ const floorEdge = Math.max(box.y + box.h, fit === undefined ? -Infinity : fit.floor.y + fit.floor.h);
556
+ const mid = cx;
495
557
  // A tunnel through the lower stands at the middle of the bench side, the camera platform above them, and the benches in the gap.
496
- stands.gaps.push({ kind: "tunnel", box: [mid - 6, bottom + off.bottom - 1, mid + 6, bottom + off.bottom + lowerRing.to + 1] });
558
+ stands.gaps.push({ kind: "tunnel", box: [mid - 6, edge, mid + 6, edge + lowerRing.to + 1] });
497
559
  const media = rings[rings.length - 1];
498
560
  if (media !== undefined && media.tier === "upper")
499
- stands.gaps.push({ kind: "camera", box: [mid - 22, bottom + off.bottom + media.from + 3, mid + 22, bottom + off.bottom + media.from + 13] });
500
- if (off.bottom - off.top >= 4 || sport === "soccer" || sport === "nfl" || sport === "cfl" || sport === "ifaf" || sport === "rugby" || sport === "gaelic") {
561
+ stands.gaps.push({ kind: "camera", box: [mid - 22, edge + media.from + 3, mid + 22, edge + media.from + 13] });
562
+ if (base.bench >= 4 || sport === "soccer" || sport === "nfl" || sport === "cfl" || sport === "ifaf" || sport === "rugby" || sport === "gaelic") {
501
563
  const benchLen = Math.min(34, box.w * 0.12);
502
- for (const s of [-1, 1]) {
503
- const bx = mid + s * (box.w * 0.1 + benchLen / 2 + 4);
504
- stands.gaps.push({ kind: "bench", box: [bx - benchLen / 2, bottom + 2.5, bx + benchLen / 2, bottom + 2.5 + Math.min(5, off.bottom * 0.3)] });
564
+ const depth = Math.min(5, off.bottom * 0.3);
565
+ if (floorEdge + 2.5 + depth <= edge - 1) {
566
+ for (const s of [-1, 1]) {
567
+ const bx = box.x + box.w / 2 + s * (box.w * 0.1 + benchLen / 2 + 4);
568
+ stands.gaps.push({ kind: "bench", box: [bx - benchLen / 2, floorEdge + 2.5, bx + benchLen / 2, floorEdge + 2.5 + depth] });
569
+ }
505
570
  }
506
571
  }
507
- stands.gaps.push({ kind: "media", box: [mid - 55, bottom + off.bottom + 2, mid + 55, bottom + off.bottom + 6] });
572
+ // The media's desks: along the straight of that side only.
573
+ const hole = lowerRing.hole;
574
+ const straight = hole?.kind === "rrect" ? (hole.x1 - hole.x0) / 2 - Math.min(hole.r, (hole.x1 - hole.x0) / 2, (hole.y1 - hole.y0) / 2) : 55;
575
+ const half = Math.min(55, straight);
576
+ if (half >= 8)
577
+ stands.gaps.push({ kind: "media", box: [mid - half, edge + 2, mid + half, edge + 6] });
508
578
  }
509
579
  }
510
580
  return stands;
@@ -1,4 +1,5 @@
1
1
  import "./full.ts";
2
2
  import "./venues.all.ts";
3
3
  import "./walk.full.ts";
4
+ import "./circuits.held.ts";
4
5
  export * from "./district.api.ts";
@@ -5,4 +5,6 @@ import "./full.js";
5
5
  import "./venues.all.js";
6
6
  // The walkable ground of every district, for the crowd outside, as the district has always had it (the lean district reads one when `loadOutside` is asked to).
7
7
  import "./walk.full.js";
8
+ // Every circuit, as a district's `circuit` option asks for one (the lean district reads them when `loadCircuits` is asked to).
9
+ import "./circuits.held.js";
8
10
  export * from "./district.api.js";
@@ -1,8 +1,15 @@
1
- export { DISTRICT_IDS, DISTRICT_MARGIN, DISTRICT_MODES, KYOGIJO_DISTRICT_STYLE, describeDistrict, districtId, districtMember, districtRing, drawDistrict, isDistrictId, metresText, planDistrict, scaleOf, unknownVenues } from "./district.ts";
2
- export type { DistrictMember, DistrictMode, DistrictOptions, DistrictPart, DistrictPlaced, DistrictPlan, DistrictVenueInput, PlacedPart } from "./district.ts";
1
+ export { DISTRICT_IDS, DISTRICT_MARGIN, DISTRICT_MODES, KYOGIJO_DISTRICT_STYLE, circuitPointIn, describeDistrict, districtId, districtMember, districtRing, drawDistrict, isDistrictId, metresText, planDistrict, scaleOf, unknownVenues } from "./district.ts";
2
+ export type { DistrictCircuit, DistrictMember, DistrictMode, DistrictOptions, DistrictPart, DistrictPlaced, DistrictPlan, DistrictVenueInput, PlacedPart } from "./district.ts";
3
3
  export { buildDistrictTurn, districtPresets, districtTurnMarkup, mountDistrictTurn } from "./district.turn.ts";
4
4
  export type { DistrictTurnSettings } from "./district.turn.ts";
5
5
  export type { ContextArea } from "./context.ts";
6
+ export { CIRCUIT_INDEX, loadCircuits } from "./circuits.index.ts";
7
+ export { circuitsHeld } from "./district.circuit.ts";
8
+ export { drawDistrictRace, mountDistrictRace } from "./district.race.ts";
9
+ export type { DistrictRaceDrawOptions, DistrictRaceOptions, DistrictRaceSettings } from "./district.race.ts";
10
+ export type { KyogijoRace, RaceState } from "./circuits.race.dom.ts";
11
+ export { LANDMARK_SHAPES } from "./landmarks.ts";
12
+ export type { ContextLandmark, LandmarkShape, PlacedLandmark } from "./landmarks.ts";
6
13
  export { KODOKAN, KODOKAN_DOJO, KODOKAN_FLOOR_HEIGHT, KODOKAN_FLOOR_IDS, KODOKAN_FLOORS, KODOKAN_ROOM_HEIGHT, kodokanLevel, kodokanMember } from "./kodokan.ts";
7
14
  export type { KodokanFloor, KodokanRoom } from "./kodokan.ts";
8
15
  export { VENUE_ATTRIBUTION } from "./venues.ts";
@@ -1,6 +1,12 @@
1
1
  // A district: several real venues of one city on one plan at their real relative positions and orientations (OpenStreetMap, ODbL 1.0), in 2D and in 3D, tight round neighbours or at the real distances of a city.
2
- export { DISTRICT_IDS, DISTRICT_MARGIN, DISTRICT_MODES, KYOGIJO_DISTRICT_STYLE, describeDistrict, districtId, districtMember, districtRing, drawDistrict, isDistrictId, metresText, planDistrict, scaleOf, unknownVenues } from "./district.js";
2
+ export { DISTRICT_IDS, DISTRICT_MARGIN, DISTRICT_MODES, KYOGIJO_DISTRICT_STYLE, circuitPointIn, describeDistrict, districtId, districtMember, districtRing, drawDistrict, isDistrictId, metresText, planDistrict, scaleOf, unknownVenues } from "./district.js";
3
3
  export { buildDistrictTurn, districtPresets, districtTurnMarkup, mountDistrictTurn } from "./district.turn.js";
4
+ // A race circuit on the same ground (`circuit`: the district entry has every circuit, the lean district reads them with `loadCircuits`), and the famous landmarks an area of context has round it.
5
+ export { CIRCUIT_INDEX, loadCircuits } from "./circuits.index.js";
6
+ export { circuitsHeld } from "./district.circuit.js";
7
+ // A race on that circuit, among the venues and the city (the circuits' race, made-up cars or a feed's): drawn at a moment, or played in a page flat or in 3D.
8
+ export { drawDistrictRace, mountDistrictRace } from "./district.race.js";
9
+ export { LANDMARK_SHAPES } from "./landmarks.js";
4
10
  export { KODOKAN, KODOKAN_DOJO, KODOKAN_FLOOR_HEIGHT, KODOKAN_FLOOR_IDS, KODOKAN_FLOORS, KODOKAN_ROOM_HEIGHT, kodokanLevel, kodokanMember } from "./kodokan.js";
5
11
  export { VENUE_ATTRIBUTION } from "./venues.js";
6
12
  export { mountDistrict } from "./district.dom.js";
@@ -0,0 +1,34 @@
1
+ import type { CircuitDrawing } from "./circuits.draw.ts";
2
+ import type { carRadius, carsMarkup } from "./circuits.race.draw.ts";
3
+ import type { mountRaceIn } from "./circuits.race.dom.ts";
4
+ import type { raceAt } from "./circuits.race.moment.ts";
5
+ import type { simulateRace } from "./circuits.race.ts";
6
+ import type { RaceWords } from "./circuits.race.words.ts";
7
+ import type { CircuitPlan } from "./circuits.ts";
8
+ import type { Circuit, CircuitOptions } from "./circuits.types.ts";
9
+ import type { CircuitWords } from "./circuits.words.ts";
10
+ import type { Language } from "./types.ts";
11
+ import type { Upright } from "./turn.kit.ts";
12
+ /** The circuits' code a district uses. */
13
+ export interface CircuitSupport {
14
+ circuitOf: (id: string) => Circuit;
15
+ circuitPlan: (circuit: Circuit) => CircuitPlan;
16
+ circuitDrawing: (options: CircuitOptions) => CircuitDrawing;
17
+ circuitUprights: (drawn: CircuitDrawing, thin: number) => Upright[];
18
+ words: Readonly<Record<Language, CircuitWords>>;
19
+ /** A race on the circuit (`mountDistrictRace`, `drawDistrictRace`): the simulation, its moments, its cars' markup and its view in a page. */
20
+ race: {
21
+ simulateRace: typeof simulateRace;
22
+ raceAt: typeof raceAt;
23
+ carsMarkup: typeof carsMarkup;
24
+ carRadius: typeof carRadius;
25
+ mountRaceIn: typeof mountRaceIn;
26
+ words: Readonly<Record<Language, RaceWords>>;
27
+ };
28
+ }
29
+ /** Fill the table: `circuits.held.ts` does it when it is imported. */
30
+ export declare function holdCircuits(support: CircuitSupport): void;
31
+ /** The circuits' code, or an error that says how to have it. */
32
+ export declare function circuitSupport(): CircuitSupport;
33
+ /** Whether the circuits are held (the district entry, or the lean district after `loadCircuits()`). */
34
+ export declare const circuitsHeld: () => boolean;
@@ -0,0 +1,13 @@
1
+ let held;
2
+ /** Fill the table: `circuits.held.ts` does it when it is imported. */
3
+ export function holdCircuits(support) {
4
+ held = support;
5
+ }
6
+ /** The circuits' code, or an error that says how to have it. */
7
+ export function circuitSupport() {
8
+ if (held === undefined)
9
+ throw new Error("a district with a circuit needs the circuits: the district entry has them, and the lean district reads them with `await loadCircuits()` first");
10
+ return held;
11
+ }
12
+ /** Whether the circuits are held (the district entry, or the lean district after `loadCircuits()`). */
13
+ export const circuitsHeld = () => held !== undefined;
@@ -3,6 +3,8 @@ import { type StadiumConfigId } from "./stadiums.ts";
3
3
  import type { OutsideOptions } from "./outside.ts";
4
4
  import type { Pt } from "./crowd.ts";
5
5
  import type { Language, SurfaceOptions } from "./types.ts";
6
+ import type { CircuitPlan } from "./circuits.ts";
7
+ import type { CircuitOptions } from "./circuits.types.ts";
6
8
  /** How a district is fitted: `neighbours` tight round venues that stand close together, `city` at the real distances between venues that are kilometres apart. */
7
9
  export type DistrictMode = "neighbours" | "city";
8
10
  export declare const DISTRICT_MODES: readonly DistrictMode[];
@@ -13,8 +15,13 @@ export type DistrictVenueInput = string | {
13
15
  };
14
16
  /** What can be asked of a district. */
15
17
  export interface DistrictOptions {
16
- /** The venues, by key: any key of `VENUES` (the arenas) and `STADIUMS` (the stadiums); at least one. */
18
+ /** The venues, by key: any key of `VENUES` (the arenas) and `STADIUMS` (the stadiums); at least one, unless the district has a `circuit`. */
17
19
  venues: readonly DistrictVenueInput[];
20
+ /**
21
+ * A race circuit laid on the same ground (a key of `CIRCUIT_IDS`: `las-vegas`): its lap, pit lane, ground and pit building where OpenStreetMap maps them, under the venues, turned as it lies; in 3D its pit building and its turns'
22
+ * numbers stand up and a lap that crosses itself stands its pass over on a bridge, as in the circuit's own view. Missing: no circuit.
23
+ */
24
+ circuit?: string;
18
25
  /** `neighbours` (the default) or `city`. */
19
26
  mode?: DistrictMode;
20
27
  /** How far the plan is turned, in degrees clockwise: 0 (the default) has north at the top; `"auto"` turns it so that the district's long way lies across the page. */
@@ -171,6 +178,19 @@ export interface DistrictGate {
171
178
  angle: number;
172
179
  basis: "documented" | "plausible";
173
180
  }
181
+ /** A circuit placed in a district: its own plan (feet round its own middle, its long side along x), and where that middle is put and how far the plan is turned to lie as it does in the world. */
182
+ export interface DistrictCircuit {
183
+ plan: CircuitPlan;
184
+ /** Where the circuit's own origin (the middle of its plan) is in the district's frame, in feet. */
185
+ at: Pt;
186
+ /** The degrees the circuit's plan is turned clockwise to lie in the district (its heading less a quarter turn, and the map's turn). */
187
+ angle: number;
188
+ /** The outline of everything it draws on the ground (its plan's box), and of its pit building, in the district's frame. */
189
+ footprint: Pt[];
190
+ building?: Pt[];
191
+ }
192
+ /** A point of a circuit's own plan (feet, `circuitPlan`) where the district puts it: for a page that draws something on the lap (a race's cars) in a district's frame. */
193
+ export declare function circuitPointIn(placed: Pick<DistrictCircuit, "at" | "angle">, p: Pt): Pt;
174
194
  /** What a district is: its venues placed, the box that fits them, and how the map lies. */
175
195
  export interface DistrictPlan {
176
196
  options: DistrictOptions;
@@ -197,8 +217,10 @@ export interface DistrictPlan {
197
217
  b: string;
198
218
  metres: number;
199
219
  }[];
200
- /** The `[longitude, latitude]` the plan's metres are measured from: the middle of the venues' surfaces. */
220
+ /** The `[longitude, latitude]` the plan's metres are measured from: the middle of the venues' surfaces (and of the circuit's plan, where there is one). */
201
221
  origin: readonly [number, number];
222
+ /** The circuit on the ground, where the options name one. */
223
+ circuit?: DistrictCircuit;
202
224
  }
203
225
  /** The margin round the buildings, as a share of the longer side of the district: tight for neighbours, wide for a city's grid and names. */
204
226
  export declare const DISTRICT_MARGIN: Readonly<Record<DistrictMode, number>>;
@@ -217,7 +239,7 @@ export declare const OUTSIDE_ROOM = 160;
217
239
  */
218
240
  export declare function planDistrict(options: DistrictOptions): DistrictPlan;
219
241
  /** The colours and the type of a district's drawing, light and dark. Defaults sit in `:where()`, so a page's own rule wins. */
220
- export declare const KYOGIJO_DISTRICT_STYLE = "\n:where(.ky-district){--ky-dist-ground:#e1e4da;--ky-dist-grid:rgba(40,46,42,.16);--ky-dist-ink:#1f2320;--ky-dist-halo:rgba(255,255,255,.9);--ky-dist-people:#c2410c;--ky-dist-b0:#d1d4cb;--ky-dist-b1:#c3c7be;--ky-dist-b2:#b2b7ae;--ky-dist-b3:#9ea49b;--ky-dist-bline:rgba(40,46,42,.28);--ky-dist-road:#cbcfc4;--ky-dist-deck:#bdc2b7;--ky-dist-walk:#eef0e8;--ky-dist-rail:#8b9288;--ky-dist-gate:#1f5fa8;--ky-dist-water:#c7d5dc;--ky-dist-green:#d2dcc4;--ky-dist-wood:#c3d0b6;--ky-dist-tree:#b3c2a3;--ky-dist-floor:#2f6db0}\n@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-district:not([data-theme=light])){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85);--ky-dist-people:#fbbf24;--ky-dist-b0:#262c31;--ky-dist-b1:#2e353b;--ky-dist-b2:#394148;--ky-dist-b3:#474f57;--ky-dist-bline:rgba(220,226,220,.22);--ky-dist-road:#2b3239;--ky-dist-deck:#353d45;--ky-dist-walk:#242b31;--ky-dist-rail:#5b646c;--ky-dist-gate:#7fb2ee;--ky-dist-water:#1c2d3b;--ky-dist-green:#202d26;--ky-dist-wood:#1c2b23;--ky-dist-tree:#2e4337;--ky-dist-floor:#7fb0e8}}\n:where(html[data-theme=dark] .ky-district,.ky-district[data-theme=dark]){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85);--ky-dist-people:#fbbf24;--ky-dist-b0:#262c31;--ky-dist-b1:#2e353b;--ky-dist-b2:#394148;--ky-dist-b3:#474f57;--ky-dist-bline:rgba(220,226,220,.22);--ky-dist-road:#2b3239;--ky-dist-deck:#353d45;--ky-dist-walk:#242b31;--ky-dist-rail:#5b646c;--ky-dist-gate:#7fb2ee;--ky-dist-water:#1c2d3b;--ky-dist-green:#202d26;--ky-dist-wood:#1c2b23;--ky-dist-tree:#2e4337;--ky-dist-floor:#7fb0e8}\n.ky-district .ky-dist-ground{fill:var(--ky-dist-ground)}\n.ky-district .ky-dist-grid{fill:none;stroke:var(--ky-dist-grid);stroke-width:1}\n.ky-district .ky-dist-block{stroke:var(--ky-dist-bline);stroke-width:.5;stroke-linejoin:round}\n.ky-district .ky-dist-b0{fill:var(--ky-dist-b0)}\n.ky-district .ky-dist-b1{fill:var(--ky-dist-b1)}\n.ky-district .ky-dist-b2{fill:var(--ky-dist-b2)}\n.ky-district .ky-dist-b3{fill:var(--ky-dist-b3)}\n.ky-district .ky-dist-street{stroke-linejoin:round;stroke-linecap:butt}\n.ky-district .ky-dist-street-0{stroke:var(--ky-dist-road)}\n.ky-district .ky-dist-street-1{stroke:var(--ky-dist-deck)}\n.ky-district .ky-dist-street-2{stroke:var(--ky-dist-rail);stroke-dasharray:6 3}\n.ky-district .ky-dist-street-3{stroke:var(--ky-dist-walk)}\n.ky-district .ky-dist-land{stroke:none}\n.ky-district .ky-dist-water{fill:var(--ky-dist-water)}\n.ky-district .ky-dist-green{fill:var(--ky-dist-green)}\n.ky-district .ky-dist-wood{fill:var(--ky-dist-wood)}\n.ky-district .ky-dist-tree{fill:none;stroke:var(--ky-dist-tree);stroke-linecap:round}\n.ky-district .ky-dist-gate rect{fill:var(--ky-dist-gate);stroke:var(--ky-dist-gate);stroke-width:2.4}\n.ky-district .ky-dist-gate-plausible rect{fill:var(--ky-dist-ground);fill-opacity:.85}\n.ky-district .ky-dist-gate-queue{fill:none;stroke:var(--ky-dist-gate);stroke-width:3;stroke-dasharray:9 7}\n.ky-district .ky-dist-ghost{fill:var(--ky-dist-b1);fill-opacity:.35}\n.ky-district .ky-dist-floor{stroke:var(--ky-dist-floor);stroke-width:2}\n.ky-district .ky-dist-ink{fill:none;stroke:var(--ky-dist-ink);stroke-linecap:round;stroke-linejoin:round}\n.ky-district .ky-dist-text{fill:var(--ky-dist-ink);font-family:var(--ky-font,system-ui,-apple-system,\"Segoe UI\",\"Hiragino Sans\",\"Noto Sans JP\",sans-serif);font-weight:600;paint-order:stroke;stroke:var(--ky-dist-halo);stroke-linejoin:round}";
242
+ export declare const KYOGIJO_DISTRICT_STYLE = "\n:where(.ky-district){--ky-dist-ground:#e1e4da;--ky-dist-grid:rgba(40,46,42,.16);--ky-dist-ink:#1f2320;--ky-dist-halo:rgba(255,255,255,.9);--ky-dist-people:#c2410c;--ky-dist-b0:#d1d4cb;--ky-dist-b1:#c3c7be;--ky-dist-b2:#b2b7ae;--ky-dist-b3:#9ea49b;--ky-dist-bline:rgba(40,46,42,.28);--ky-dist-road:#cbcfc4;--ky-dist-deck:#bdc2b7;--ky-dist-walk:#eef0e8;--ky-dist-rail:#8b9288;--ky-dist-gate:#1f5fa8;--ky-dist-water:#c7d5dc;--ky-dist-green:#d2dcc4;--ky-dist-wood:#c3d0b6;--ky-dist-tree:#b3c2a3;--ky-dist-floor:#2f6db0}\n@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-district:not([data-theme=light])){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85);--ky-dist-people:#fbbf24;--ky-dist-b0:#262c31;--ky-dist-b1:#2e353b;--ky-dist-b2:#394148;--ky-dist-b3:#474f57;--ky-dist-bline:rgba(220,226,220,.22);--ky-dist-road:#2b3239;--ky-dist-deck:#353d45;--ky-dist-walk:#242b31;--ky-dist-rail:#5b646c;--ky-dist-gate:#7fb2ee;--ky-dist-water:#1c2d3b;--ky-dist-green:#202d26;--ky-dist-wood:#1c2b23;--ky-dist-tree:#2e4337;--ky-dist-floor:#7fb0e8}}\n:where(html[data-theme=dark] .ky-district,.ky-district[data-theme=dark]){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85);--ky-dist-people:#fbbf24;--ky-dist-b0:#262c31;--ky-dist-b1:#2e353b;--ky-dist-b2:#394148;--ky-dist-b3:#474f57;--ky-dist-bline:rgba(220,226,220,.22);--ky-dist-road:#2b3239;--ky-dist-deck:#353d45;--ky-dist-walk:#242b31;--ky-dist-rail:#5b646c;--ky-dist-gate:#7fb2ee;--ky-dist-water:#1c2d3b;--ky-dist-green:#202d26;--ky-dist-wood:#1c2b23;--ky-dist-tree:#2e4337;--ky-dist-floor:#7fb0e8}\n.ky-district .ky-dist-ground{fill:var(--ky-dist-ground)}\n.ky-district .ky-dist-grid{fill:none;stroke:var(--ky-dist-grid);stroke-width:1}\n.ky-district .ky-dist-block{stroke:var(--ky-dist-bline);stroke-width:.5;stroke-linejoin:round}\n.ky-district .ky-dist-b0{fill:var(--ky-dist-b0)}\n.ky-district .ky-dist-b1{fill:var(--ky-dist-b1)}\n.ky-district .ky-dist-b2{fill:var(--ky-dist-b2)}\n.ky-district .ky-dist-b3{fill:var(--ky-dist-b3)}\n.ky-district .ky-dist-street{stroke-linejoin:round;stroke-linecap:butt}\n.ky-district .ky-dist-street-0{stroke:var(--ky-dist-road)}\n.ky-district .ky-dist-street-1{stroke:var(--ky-dist-deck)}\n.ky-district .ky-dist-street-2{stroke:var(--ky-dist-rail);stroke-dasharray:6 3}\n.ky-district .ky-dist-street-3{stroke:var(--ky-dist-walk)}\n.ky-district .ky-dist-land{stroke:none}\n.ky-district .ky-dist-water{fill:var(--ky-dist-water)}\n.ky-district .ky-dist-green{fill:var(--ky-dist-green)}\n.ky-district .ky-dist-wood{fill:var(--ky-dist-wood)}\n.ky-district .ky-dist-tree{fill:none;stroke:var(--ky-dist-tree);stroke-linecap:round}\n.ky-district .ky-dist-jet{fill:none;stroke:var(--ky-dist-halo);stroke-linecap:round}\n.ky-district .ky-dist-gate rect{fill:var(--ky-dist-gate);stroke:var(--ky-dist-gate);stroke-width:2.4}\n.ky-district .ky-dist-gate-plausible rect{fill:var(--ky-dist-ground);fill-opacity:.85}\n.ky-district .ky-dist-gate-queue{fill:none;stroke:var(--ky-dist-gate);stroke-width:3;stroke-dasharray:9 7}\n.ky-district .ky-dist-ghost{fill:var(--ky-dist-b1);fill-opacity:.35}\n.ky-district .ky-dist-floor{stroke:var(--ky-dist-floor);stroke-width:2}\n.ky-district .ky-dist-ink{fill:none;stroke:var(--ky-dist-ink);stroke-linecap:round;stroke-linejoin:round}\n.ky-district .ky-dist-text{fill:var(--ky-dist-ink);font-family:var(--ky-font,system-ui,-apple-system,\"Segoe UI\",\"Hiragino Sans\",\"Noto Sans JP\",sans-serif);font-weight:600;paint-order:stroke;stroke:var(--ky-dist-halo);stroke-linejoin:round}";
221
243
  /**
222
244
  * A venue's drawing as a part of the district's: its root made a nested `svg` that keeps the class, the sport and the colours the drawing's stylesheet reads, its box laid in its own frame (so that the group's turn
223
245
  * and shift put it in place), and its title, description, apron, arrow and stylesheet taken out (the stylesheet is given back in `styles`).
@@ -263,5 +285,7 @@ export declare function drawMember(member: DistrictMember, options: DistrictOpti
263
285
  * ```
264
286
  */
265
287
  export declare function drawDistrict(options: DistrictOptions, plan?: DistrictPlan, drawing?: (member: DistrictMember, index: number, part: number) => string): string;
288
+ /** The options a district's circuit is drawn from: the circuit's own drawing in the district's language and theme, without its frame, and for the floor of the 3D view without its turns' numbers (they stand up). */
289
+ export declare function circuitInDistrict(options: DistrictOptions, id: string, floor: boolean): CircuitOptions;
266
290
  /** The venues a district was asked for that the package has no key for: none when it can be drawn. */
267
291
  export declare const unknownVenues: (venues: readonly DistrictVenueInput[]) => string[];