@johnmorrisdotca/kyogijo-stadiums 0.12.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/turn.dom.js CHANGED
@@ -30,16 +30,24 @@ const between = (from, to, t) => ({ yaw: from.yaw + (nearest(from.yaw, to.yaw) -
30
30
  * ```
31
31
  */
32
32
  export function mountKyogijoTurn(host, options, settings = {}) {
33
+ return mountTurn(host, options, settings, buildTurn);
34
+ }
35
+ /**
36
+ * Mount any 3D model (`mountKyogijoTurn` is this with the model of one surface; `mountMultiTurn` with a tournament layout's): `build` makes the model from what `update` is given, and the rest, the controls, the pointer, the keys
37
+ * and the auto-turn, is the same.
38
+ */
39
+ export function mountTurn(host, options, settings, build) {
33
40
  const doc = host.ownerDocument;
34
41
  const win = doc.defaultView;
35
- let model = buildTurn(options, settings);
36
- const sport = () => model.options.sport;
42
+ let model = build(options, settings);
43
+ /** What names the four viewpoints: the model's own table (a tournament layout has one), else the sport's. */
44
+ const sport = () => model.presets ?? model.options.sport;
37
45
  const supported = turnSupported(win ?? undefined) && model.turnable;
38
46
  let alive = true;
39
47
  if (!supported) {
40
48
  // The flat drawing, with a sentence that says why it does not turn when the browser is the reason.
41
49
  const flat = (next) => {
42
- host.innerHTML = `<div class="ky-turn" data-mode="flat"><style>${KYOGIJO_TURN_STYLE}</style>${drawKyogijo(floorOptions(next))}${model.turnable ? `<p class="ky-turn-flat">${esc(turnWords(model).flat)}</p>` : ""}</div>`;
50
+ host.innerHTML = `<div class="ky-turn" data-mode="flat"><style>${KYOGIJO_TURN_STYLE}</style>${model.flat ?? drawKyogijo(floorOptions(next))}${model.turnable ? `<p class="ky-turn-flat">${esc(turnWords(model).flat)}</p>` : ""}</div>`;
43
51
  };
44
52
  flat(options);
45
53
  return {
@@ -53,7 +61,7 @@ export function mountKyogijoTurn(host, options, settings = {}) {
53
61
  pause: () => undefined,
54
62
  togglePlay: () => false,
55
63
  update: (next) => {
56
- model = buildTurn(next, settings);
64
+ model = build(next, settings);
57
65
  flat(next);
58
66
  },
59
67
  svg: () => (alive ? host.querySelector("svg.kyogijo") : null),
@@ -430,7 +438,7 @@ export function mountKyogijoTurn(host, options, settings = {}) {
430
438
  update(next) {
431
439
  if (!alive)
432
440
  return;
433
- model = buildTurn(next, { ...settings, preset: undefined, view });
441
+ model = build(next, { ...settings, preset: undefined, view });
434
442
  view = clampView(view);
435
443
  opening = openingView(settings, sport());
436
444
  const layout = layoutOf(model, shown, stage.clientWidth || 960, stage.clientHeight || 600, settings.lens);
@@ -1,7 +1,7 @@
1
1
  /** A point or a direction: x, y, z in feet. */
2
2
  export type V3 = readonly [number, number, number];
3
3
  /** What a plane is a part of: it is the class of its element (`ky-up-<kind>`) and what a test finds it by. */
4
- export type PartKind = "post" | "net" | "tape" | "antenna" | "board" | "glass" | "rim" | "stanchion" | "frame" | "goal" | "flag" | "stump" | "leg" | "table" | "pylon" | "wall" | "screen" | "pole" | "ground" | "target" | "seats" | "concourse" | "fascia" | "roof" | "banner" | "platform" | "rail" | "matedge" | "shrine";
4
+ export type PartKind = "post" | "net" | "tape" | "antenna" | "board" | "glass" | "rim" | "stanchion" | "frame" | "goal" | "flag" | "stump" | "leg" | "table" | "pylon" | "wall" | "screen" | "pole" | "ground" | "target" | "seats" | "concourse" | "fascia" | "roof" | "banner" | "platform" | "rail" | "matedge" | "shrine" | "surround" | "chair" | "desk";
5
5
  /** One flat rectangle. Its own x runs along `u` (its whole length) and its own y along `v`; `o` is the corner both start from. `svg` is what is drawn on it, in feet along `u` and `v`. */
6
6
  export interface Plane {
7
7
  /** A name that says what it is and where: `net`, `post-left`, `goal-right-back`. */
@@ -28,9 +28,11 @@ export interface Plane {
28
28
  crop?: boolean;
29
29
  /** How much of a `shy` plane is left when it is cut away altogether: 0.08 when missing. A plane that others lie over on the screen (the steps of a gallery) takes less, so that they do not add up to a veil. */
30
30
  fadeTo?: number;
31
+ /** How low the eye must be for a `shy` plane to begin to give way: the share (of the plane's side being towards the eye, times the sine of the pitch) at which the fade starts; 0.1 when missing. A low barrier takes a high one, so that it stays whole until the eye is nearly level with it. */
32
+ fadeAt?: number;
31
33
  }
32
34
  /** What stands on the surface as a thing a reader would name: a net, a basket, a goal, a pair of posts. */
33
- export type UprightKind = "scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets" | "stands" | "wall-seats" | "roof" | "gallery" | "dais" | "banner" | "shrine" | "mat-edge";
35
+ export type UprightKind = "scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets" | "stands" | "wall-seats" | "roof" | "gallery" | "dais" | "banner" | "shrine" | "mat-edge" | "surrounds" | "officials";
34
36
  /** A thing that stands on the surface: where, how high, and the planes it is made of. */
35
37
  export interface Upright {
36
38
  id: string;
@@ -90,4 +92,4 @@ export declare function shy(p: Plane, centre: readonly [number, number]): Plane;
90
92
  * How much of a wall is left (1 whole, down to 0.08) at a view: none is cut away unless the wall is on the eye's side of the surface, and then more the lower the eye is, because a wall seen
91
93
  * from straight above hides nothing. `yaw` and `pitch` are the view's, in degrees.
92
94
  */
93
- export declare function fadeOf(p: Pick<Plane, "shy" | "fadeTo">, yaw: number, pitch: number): number;
95
+ export declare function fadeOf(p: Pick<Plane, "shy" | "fadeTo" | "fadeAt">, yaw: number, pitch: number): number;
package/dist/turn.kit.js CHANGED
@@ -105,6 +105,7 @@ export function fadeOf(p, yaw, pitch) {
105
105
  // The horizontal direction from the surface to the eye (the same sum as the depth of a point).
106
106
  const toward = p.shy[0] * Math.sin(a) + p.shy[1] * Math.cos(a);
107
107
  const hides = toward * Math.sin((pitch * Math.PI) / 180);
108
- const t = Math.min(1, Math.max(0, (hides - 0.1) / 0.25));
108
+ const from = p.fadeAt ?? 0.1;
109
+ const t = Math.min(1, Math.max(0, (hides - from) / 0.25));
109
110
  return Math.round((1 - (1 - (p.fadeTo ?? 0.08)) * t) * 1000) / 1000;
110
111
  }
@@ -88,7 +88,7 @@ export function worldMarkup(model, layout) {
88
88
  /** The buttons under the view. */
89
89
  export function controlsMarkup(model, view, playing = false) {
90
90
  const words = turnWords(model);
91
- const here = presetOf(view, model.options.sport);
91
+ const here = presetOf(view, model.presets ?? model.options.sport);
92
92
  const presets = TURN_PRESET_NAMES.map((name, i) => `<button type="button" data-preset="${name}" aria-pressed="${here === name}" aria-keyshortcuts="${i + 1}">${esc(words[name])}</button>`).join("");
93
93
  const tools = [
94
94
  ["turn-left", "↺", words.turnLeft],
@@ -115,7 +115,7 @@ export function turnMarkupOf(model, settings = {}) {
115
115
  const sheet = model.options.style === "none" ? "" : `<style>${KYOGIJO_TURN_STYLE}</style>`;
116
116
  const theme = model.options.theme;
117
117
  const sport = model.options.sport;
118
- return (`<div class="ky-turn" id="${esc(model.id)}" data-sport="${esc(String(sport))}" data-mode="3d" data-touch="${settings.touch === "turn" ? "turn" : "scroll"}" data-preset="${presetOf(view, sport) ?? "custom"}"${theme === "light" || theme === "dark" ? ` data-theme="${theme}"` : ""} lang="${model.language}">` +
118
+ return (`<div class="ky-turn" id="${esc(model.id)}" data-sport="${esc(String(sport))}" data-mode="3d" data-touch="${settings.touch === "turn" ? "turn" : "scroll"}" data-preset="${presetOf(view, model.presets ?? sport) ?? "custom"}"${theme === "light" || theme === "dark" ? ` data-theme="${theme}"` : ""} lang="${model.language}">` +
119
119
  sheet +
120
120
  `<div class="ky-turn-view" tabindex="0" role="group" aria-roledescription="${esc(words.region)}" aria-label="${esc(model.title)}" aria-describedby="${esc(model.id)}-help" aria-keyshortcuts="ArrowLeft ArrowRight ArrowUp ArrowDown + - 0 1 2 3 4 Home Space"><div ${worldAttributes(model)}>${worldMarkup(model, layout)}</div></div>` +
121
121
  `<p class="ky-turn-help" id="${esc(model.id)}-help">${esc(keys)}</p>` +
@@ -1,5 +1,5 @@
1
1
  import { KYOGIJO_STRINGS } from "./strings.ts";
2
- import { type TurnPreset, type TurnView } from "./turn.camera.ts";
2
+ import { type PresetTable, type TurnPreset, type TurnView } from "./turn.camera.ts";
3
3
  import type { Plane } from "./turn.kit.ts";
4
4
  import { type UprightOptions, type Uprights } from "./uprights.ts";
5
5
  import type { VenueSettings } from "./venue.ts";
@@ -93,11 +93,25 @@ export interface TurnModel {
93
93
  view: TurnView;
94
94
  /** False when there is nothing to turn: a badge is a strip, not a surface. */
95
95
  turnable: boolean;
96
+ /** The four viewpoints, when the model has its own (a tournament layout's); the sport's when missing. */
97
+ presets?: PresetTable;
98
+ /** The flat drawing to show where the 3D view cannot be drawn, when it is not `drawKyogijo`'s of `options` (a tournament layout's). */
99
+ flat?: string;
96
100
  }
97
101
  /** The options with what makes the floor of a 3D view: the whole surface, lying wide, at its full size. */
98
102
  export declare function floorOptions(options: SurfaceOptions | ScoreOptions): SurfaceOptions | ScoreOptions;
99
103
  /** The view a set of settings opens on. */
100
- export declare const openingView: (settings: Pick<TurnSettings, "preset" | "view">, sport?: string) => TurnView;
104
+ export declare const openingView: (settings: Pick<TurnSettings, "preset" | "view">, sport?: string | PresetTable) => TurnView;
105
+ /**
106
+ * The scene's extent in pixels from its middle: the area it is fitted to (in feet, in the drawing's frame) widened to take in every plane that is not the ground, and as high as the highest of them (or of `lift`, the height
107
+ * of the floor itself).
108
+ */
109
+ export declare function sceneBox(planes: readonly Plane[], area: {
110
+ x0: number;
111
+ y0: number;
112
+ x1: number;
113
+ y1: number;
114
+ }, lift: number, centre: readonly [number, number], scale: number): Box3;
101
115
  /** Work out a 3D view from the options of a drawing. */
102
116
  export declare function buildTurn(options: SurfaceOptions | ScoreOptions, settings?: TurnSettings): TurnModel;
103
117
  /** The words of the 3D view in the model's language. */
@@ -33,6 +33,38 @@ function crowdVars(floor) {
33
33
  }
34
34
  /** The view a set of settings opens on. */
35
35
  export const openingView = (settings, sport) => clampView({ ...(presetView(settings.preset, sport) ?? presetView(TURN_DEFAULT, sport)), ...settings.view });
36
+ /**
37
+ * The scene's extent in pixels from its middle: the area it is fitted to (in feet, in the drawing's frame) widened to take in every plane that is not the ground, and as high as the highest of them (or of `lift`, the height
38
+ * of the floor itself).
39
+ */
40
+ export function sceneBox(planes, area, lift, centre, scale) {
41
+ let [x0, y0, x1, y1] = [area.x0, area.y0, area.x1, area.y1];
42
+ let top = lift;
43
+ for (const p of planes) {
44
+ // A billboard turns about its middle: whichever way it faces, it lies within a circle as wide as it is.
45
+ const reach = p.billboard === true ? Math.hypot(...p.u) / 2 : 0;
46
+ for (const [a, b] of [[0, 0], [1, 0], [0, 1], [1, 1]]) {
47
+ const point = [p.o[0] + a * p.u[0] + b * p.v[0], p.o[1] + a * p.u[1] + b * p.v[1], p.o[2] + a * p.u[2] + b * p.v[2]];
48
+ if (reach > 0) {
49
+ const middle = [p.o[0] + (p.u[0] + p.v[0]) / 2, p.o[1] + (p.u[1] + p.v[1]) / 2];
50
+ x0 = Math.min(x0, middle[0] - reach);
51
+ x1 = Math.max(x1, middle[0] + reach);
52
+ y0 = Math.min(y0, middle[1] - reach);
53
+ y1 = Math.max(y1, middle[1] + reach);
54
+ top = Math.max(top, point[2]);
55
+ continue;
56
+ }
57
+ if (p.kind !== "ground") {
58
+ x0 = Math.min(x0, point[0]);
59
+ x1 = Math.max(x1, point[0]);
60
+ y0 = Math.min(y0, point[1]);
61
+ y1 = Math.max(y1, point[1]);
62
+ }
63
+ top = Math.max(top, point[2]);
64
+ }
65
+ }
66
+ return { x0: (x0 - centre[0]) * scale, x1: (x1 - centre[0]) * scale, y0: (y0 - centre[1]) * scale, y1: (y1 - centre[1]) * scale, z0: 0, z1: top * scale };
67
+ }
36
68
  /** Work out a 3D view from the options of a drawing. */
37
69
  export function buildTurn(options, settings = {}) {
38
70
  const asked = floorOptions(options);
@@ -67,32 +99,7 @@ export function buildTurn(options, settings = {}) {
67
99
  // The scene's extent: the playing surface (the whole floor where it has no uprights), and whatever stands beyond it (a net behind a goal line). The ground
68
100
  // round the surface is not counted, so that the view is fitted to what is played on and the ground runs out of the picture.
69
101
  const area = uprights.whole === true || uprights.area === undefined ? { x0: vx, y0: vy, x1: vx + vw, y1: vy + vh } : uprights.area;
70
- let [x0, y0, x1, y1] = [area.x0, area.y0, area.x1, area.y1];
71
- let top = uprights.lift;
72
- for (const p of planes) {
73
- // A billboard turns about its middle: whichever way it faces, it lies within a circle as wide as it is.
74
- const reach = p.billboard === true ? Math.hypot(...p.u) / 2 : 0;
75
- for (const [a, b] of [[0, 0], [1, 0], [0, 1], [1, 1]]) {
76
- const point = [p.o[0] + a * p.u[0] + b * p.v[0], p.o[1] + a * p.u[1] + b * p.v[1], p.o[2] + a * p.u[2] + b * p.v[2]];
77
- if (reach > 0) {
78
- const middle = [p.o[0] + (p.u[0] + p.v[0]) / 2, p.o[1] + (p.u[1] + p.v[1]) / 2];
79
- x0 = Math.min(x0, middle[0] - reach);
80
- x1 = Math.max(x1, middle[0] + reach);
81
- y0 = Math.min(y0, middle[1] - reach);
82
- y1 = Math.max(y1, middle[1] + reach);
83
- top = Math.max(top, point[2]);
84
- continue;
85
- }
86
- if (p.kind !== "ground") {
87
- x0 = Math.min(x0, point[0]);
88
- x1 = Math.max(x1, point[0]);
89
- y0 = Math.min(y0, point[1]);
90
- y1 = Math.max(y1, point[1]);
91
- }
92
- top = Math.max(top, point[2]);
93
- }
94
- }
95
- const box = { x0: (x0 - centre[0]) * scale, x1: (x1 - centre[0]) * scale, y0: (y0 - centre[1]) * scale, y1: (y1 - centre[1]) * scale, z0: 0, z1: top * scale };
102
+ const box = sceneBox(planes, area, uprights.lift, centre, scale);
96
103
  const { title, description } = describe(o);
97
104
  const standing = describeUprights(o, uprights, language);
98
105
  return {
@@ -99,6 +99,10 @@ export const KYOGIJO_TURN_STYLE = `
99
99
  .ky-turn .u-seatback{fill:var(--ky-up-seatb)}
100
100
  .ky-turn .u-railline{stroke:var(--ky-up-railw);stroke-width:.3;stroke-linecap:square}
101
101
  .ky-turn .u-matedge{fill:var(--ky-up-matedge)}
102
+ .ky-turn .u-surround{fill:var(--ky-mu-barrier,#161a1f);fill-opacity:.92}
103
+ .ky-turn .u-surround-rim{fill:var(--ky-mu-chair,#ece7da);fill-opacity:.55}
104
+ .ky-turn .u-chair{fill:var(--ky-mu-chair,#ece7da);stroke:var(--ky-mu-chair-edge,#4a4f56);stroke-width:.06}
105
+ .ky-turn .u-chair-side{fill:color-mix(in srgb,var(--ky-mu-chair,#ece7da) 62%,#000);stroke:var(--ky-mu-chair-edge,#4a4f56);stroke-width:.04}
102
106
  .ky-turn-controls{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;align-items:center}
103
107
  .ky-turn-group{display:flex;flex-wrap:wrap;gap:6px}
104
108
  .ky-turn-controls button{font:inherit;font-size:.9rem;font-weight:600;min-width:44px;min-height:44px;padding:0 12px;border-radius:999px;border:1px solid var(--ky-turn-rule);background:var(--ky-turn-chip);color:var(--ky-turn-ink);cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
package/dist/venue.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type Crowd, type CrowdStands } from "./crowd.ts";
1
2
  import { type Cut } from "./venue.geom.ts";
2
3
  import { type Upright } from "./turn.kit.ts";
3
4
  import type { SurfaceOptions } from "./types.ts";
@@ -29,3 +30,8 @@ export declare const ROOF: {
29
30
  };
30
31
  /** The stands of a surface as planes: `undefined`-free, an empty venue when the options have no crowd (or the crowd is a hall's gallery, which `venue.room.ts` raises). */
31
32
  export declare function standsVenue(o: SurfaceOptions, v: VenueBuild, lift: number): Venue;
33
+ /**
34
+ * The stands the model says (`crowdStands`, or the stands of a tournament layout's hall) as planes: what `standsVenue` makes of a surface's, for stands that were worked out some other way. `o` is the little of the
35
+ * drawing's options it reads: the sport (whether a roof is a lid), the detail (`simple` is the plain crowd) and the ground (whose capacity sets the density).
36
+ */
37
+ export declare function standsVenueOf(stands: CrowdStands, crowd: Crowd, o: Pick<SurfaceOptions, "sport" | "detail" | "ground">, v: VenueBuild, lift: number): Venue;
package/dist/venue.js CHANGED
@@ -33,6 +33,13 @@ export function standsVenue(o, v, lift) {
33
33
  const crowd = resolveCrowd(o.crowd);
34
34
  if (stands === undefined || crowd === undefined || stands.gallery === true)
35
35
  return NO_VENUE;
36
+ return standsVenueOf(stands, crowd, { sport: o.sport, detail: o.detail, ground: o.ground }, v, lift);
37
+ }
38
+ /**
39
+ * The stands the model says (`crowdStands`, or the stands of a tournament layout's hall) as planes: what `standsVenue` makes of a surface's, for stands that were worked out some other way. `o` is the little of the
40
+ * drawing's options it reads: the sport (whether a roof is a lid), the detail (`simple` is the plain crowd) and the ground (whose capacity sets the density).
41
+ */
42
+ export function standsVenueOf(stands, crowd, o, v, lift) {
36
43
  const cut = v.detail ?? "auto";
37
44
  const fine = crowd.detail !== "plain" && o.detail !== "simple";
38
45
  const density = densityOf(crowd, stands.seats, groundById(o.ground)?.capacity);
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export declare const VERSION = "0.12.2";
2
+ export declare const VERSION = "0.14.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export const VERSION = "0.12.2";
2
+ export const VERSION = "0.14.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/kyogijo-stadiums",
3
- "version": "0.12.2",
3
+ "version": "0.14.0",
4
4
  "description": "Kyogijo Stadiums 競技場, 39 sports' surfaces as SVG, drawn to scale from the rulebooks: NFL, CFL and IFAF football fields, an NHL rink, an NBA court, MLB ballparks, soccer pitches, curling sheets, athletics stadiums, speed skating, short track and figure skating rinks, swimming pools, water polo fields, diving wells, artistic swimming areas, and the courts and fields of tennis, volleyball (indoor, beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet, netball, lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, and the budo of judo, karate, kendo, sumo and kyudo with a dojo of tatami laid as real units, with each team's name and logo on its side and the score in the middle, stands and a crowd, a pageant of flags, and a 3D view. Light and dark, accessible, a compact badge for lists, a helper for React and a custom element. Zero dependencies.",
5
5
  "keywords": [
6
6
  "sports",