@johnmorrisdotca/kyogijo-stadiums 0.16.1 → 0.17.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.
@@ -0,0 +1,152 @@
1
+ // A judging assignment in 3D: the 3D view of `turn.ts` over the package's own judo floor (`drawKyogijo`, with as many areas side by side as there are mats), with each judge's chair and the judges' table stood up, a name
2
+ // card on the table in front of every judge who has a name (turned to face the eye, so that it is read from wherever the view is) and the name of each mat on a sign over its judges' table. Nothing is drawn a second
3
+ // way: the card is the plan's card (`judging.card.ts`), fitted by the same rule. The model it makes is a `TurnModel`, so the camera, the controls, the pointer, the auto-turn and the tests of every other 3D view are this one's.
4
+ import { budoPlan } from "./budo/plan.js";
5
+ import { drawKyogijo } from "./draw.js";
6
+ import { hash } from "./format.js";
7
+ import { FEET_PER_METRE as FT } from "./geometry.js";
8
+ import { TURN_CARD, badge, card } from "./judging.card.js";
9
+ import { JUDGING_LEAGUE } from "./judging.data.js";
10
+ import { fitCard, fitSeats } from "./judging.fit.js";
11
+ import { benchOf, judgingSetting } from "./judging.js";
12
+ import { judgingDrawing, judgingText } from "./judging.draw.js";
13
+ import { JUDGING_WORDS } from "./judging.words.js";
14
+ import { mountTurn } from "./turn.dom.js";
15
+ import { turnMarkupOf } from "./turn.markup.js";
16
+ import { TURN_PRESETS } from "./turn.camera.js";
17
+ import { WORLD_PX, openingView, sceneBox } from "./turn.model.js";
18
+ import { box, n, plane, pole, rect, thinnest } from "./turn.kit.js";
19
+ import { planesOf } from "./uprights.js";
20
+ /** How high things stand, in metres (ours: no rule gives them): the table's top, a chair's seat, a name card on the table, the sign over it. */
21
+ export const JUDGING_HEIGHTS = Object.freeze({ table: 0.75, chair: 0.45, card: 0.95, sign: 0.7, signAbove: 0.25 });
22
+ /** The lens of a view close up on several benches at once: the scene turns about the middle of them, and a bench several metres from it would swell as it swung toward the eye, so the lens is long (the default is 8). */
23
+ const BENCHES_LENS = 60;
24
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
25
+ /**
26
+ * The four viewpoints of a judging view. A card stands upright, so a high eye squashes its type: the broadcast view is from the mat side, so that the judges' table is at the far side and the cards face the eye, at a
27
+ * pitch where the type is still two thirds as tall as it is wide; the others are as every surface's but for the pitch of the corner and the end.
28
+ */
29
+ export const JUDGING_PRESETS = { overhead: TURN_PRESETS.overhead, broadcast: { yaw: 0, pitch: 42, zoom: 1 }, end: { yaw: -90, pitch: 50, zoom: 1 }, corner: { yaw: -35, pitch: 48, zoom: 1 } };
30
+ /**
31
+ * A judging assignment as a 3D model: the judo floor with every mat's judges' chairs and table, a name card on the table at each seat that has a name and fits one, a number badge at the others, and the mat's name on a sign.
32
+ * A `TurnModel` like any other: `turnMarkupOf` and `mountJudgingTurn` make a view of it. `mat` in the options builds one mat alone (a tab); without it the mats stand side by side.
33
+ */
34
+ export function buildJudgingTurn(options, settings = {}) {
35
+ const setting = judgingSetting(options);
36
+ const language = setting.language;
37
+ const words = JUDGING_WORDS[language];
38
+ const league = JUDGING_LEAGUE[setting.style].league;
39
+ const count = setting.mats.length;
40
+ const id = settings.id ?? (typeof options.id === "string" && options.id !== "" ? `${options.id}-turn` : `ky-turn-${hash(JSON.stringify([options, settings.preset]))}`);
41
+ const floorOptions = { sport: "judo", league, budo: { style: setting.style, officials: true, areas: count }, language, margin: 2, id: `${id}-floor`, ...(options.theme === undefined ? {} : { theme: options.theme }), ...(options.css === "none" ? { style: "none" } : {}) };
42
+ const floor = drawKyogijo({ ...floorOptions, orientation: "landscape" });
43
+ const found = /viewBox="(-?[\d.]+) (-?[\d.]+) ([\d.]+) ([\d.]+)"/.exec(floor);
44
+ if (found === null)
45
+ throw new Error("the floor of a 3D view must be an SVG with a viewBox");
46
+ const [vx, vy, vw, vh] = found.slice(1).map(Number);
47
+ const plan = budoPlan({ sport: "judo", league, budo: { style: setting.style, officials: true, areas: count } });
48
+ const judges = plan.judo.judges;
49
+ const seatsPerMat = setting.mats[0].seats.length;
50
+ // The plan's own frame is metres from the top left of the tatami, and the drawing's is feet from the same corner.
51
+ const ft = (m) => m * FT;
52
+ const H = JUDGING_HEIGHTS;
53
+ const items = [];
54
+ const tableTop = ft(H.table);
55
+ const pitch = judges.seats.length > 1 ? judges.seats[1].x0 - judges.seats[0].x0 : 1.25;
56
+ const cardW = ft(pitch - 0.06);
57
+ const cardH = ft(H.card);
58
+ const pad = Math.max(0.08, cardW * 0.05);
59
+ const r = clamp(cardW * 0.2, 0.2, 0.45);
60
+ const innerW = cardW - 2 * pad;
61
+ const innerH = cardH - 2 * pad - r;
62
+ const minName = 0.62;
63
+ setting.mats.forEach((mat, mi) => {
64
+ const table = judges.tables[mi];
65
+ const fitted = fitSeats(mat.seats, { width: innerW, height: innerH, max: Math.min(cardW * 0.3, 0.75), min: minName, idLabel: words.chart.idLabel });
66
+ const furniture = [];
67
+ furniture.push(...box(`m${mat.n}-table`, "desk", rect(ft(table.x0), ft(table.y0), ft(table.x1 - table.x0), ft(table.y1 - table.y0)), tableTop, "u-bench"));
68
+ const cards = [];
69
+ mat.seats.forEach((a, si) => {
70
+ const chair = judges.seats[mi * seatsPerMat + si];
71
+ furniture.push(...box(`m${mat.n}-chair-${a.seat.n}`, "chair", rect(ft(chair.x0), ft(chair.y0), ft(chair.x1 - chair.x0), ft(chair.y1 - chair.y0)), ft(H.chair), "u-bench"));
72
+ const cx = ft((chair.x0 + chair.x1) / 2);
73
+ const cy = ft((table.y0 + table.y1) / 2);
74
+ const fit = fitted[si];
75
+ if (fit !== undefined) {
76
+ const p = plane(`m${mat.n}-card-${a.seat.n}`, "card", [cx - cardW / 2, cy, tableTop + cardH], [cardW, 0, 0], [0, 0, -cardH], card(TURN_CARD, fit, a.seat.n, 0, 0, cardW, cardH, pad, r, 0.1));
77
+ cards.push({ ...p, billboard: true });
78
+ }
79
+ else {
80
+ // Nobody in the seat, or a name that does not fit a card: the seat's number alone, on a small badge, with the name in the chart.
81
+ const br = 0.55;
82
+ const p = plane(`m${mat.n}-badge-${a.seat.n}`, "card", [cx - br, cy, tableTop + 2 * br], [2 * br, 0, 0], [0, 0, -2 * br], badge(TURN_CARD, br, br, br * 0.92, a.seat.n, a.assigned));
83
+ cards.push({ ...p, billboard: true });
84
+ }
85
+ });
86
+ // The mat's name on a sign over the whole bench, on a pole at each end of the table.
87
+ const midX = ft((table.x0 + table.x1) / 2);
88
+ const midY = ft((table.y0 + table.y1) / 2);
89
+ const signW = ft(pitch * seatsPerMat);
90
+ const signH = ft(H.sign);
91
+ const signBottom = tableTop + cardH + ft(H.signAbove);
92
+ const text = fitCard({ name: mat.label, id: "", idLabel: "", width: signW * 0.9, height: signH * 0.86, max: signH * 0.62, min: signH * 0.3, maxLines: 2, breakWords: true });
93
+ const lines = text?.name ?? [mat.label];
94
+ const size = text?.nameSize ?? signH * 0.3;
95
+ const label = lines.map((line, i) => `<text class="u-sign-text" x="${n(signW / 2)}" y="${n(signH / 2 + (i - (lines.length - 1) / 2) * size * 1.1 + size * 0.34)}" font-size="${n(size)}">${line.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}</text>`).join("");
96
+ const sign = { ...plane(`m${mat.n}-sign`, "sign", [midX - signW / 2, midY, signBottom + signH], [signW, 0, 0], [0, 0, -signH], `<rect class="u-sign" width="${n(signW)}" height="${n(signH)}" rx="${n(signH * 0.12)}"/>${label}`), billboard: true };
97
+ const poles = [table.x0, table.x1].flatMap((px, k) => pole(`m${mat.n}-sign-pole-${k + 1}`, "pole", ft(px), midY, tableTop, signBottom + signH * 0.5, ft(0.06), "u-pole"));
98
+ items.push({ id: `m${mat.n}-furniture`, kind: "officials", at: [midX, midY], height: tableTop, planes: furniture });
99
+ items.push({ id: `m${mat.n}-judges`, kind: "judges", at: [midX, midY], height: tableTop + cardH, planes: [...cards, ...poles, sign] });
100
+ });
101
+ // The view is fitted to the whole floor, or to the judges' bench close up (the scene then turns about the bench, so that the cards stay where they are read).
102
+ const near = setting.focus === "bench" ? benchOf(judges.seats.slice(0, count * seatsPerMat), judges.tables[0], pitch, plan.judo.contests[0].y0, 2.2) : undefined;
103
+ const area = near === undefined ? { x0: vx, y0: vy, x1: vx + vw, y1: vy + vh } : { x0: ft(near.x0), y0: ft(near.y0), x1: ft(near.x1), y1: ft(near.y1) };
104
+ const uprights = { items, lift: 0, thin: thinnest(vw, vh), area, whole: true };
105
+ const planes = planesOf(uprights);
106
+ const scale = WORLD_PX / Math.max(vw, vh);
107
+ const centre = [(area.x0 + area.x1) / 2, (area.y0 + area.y1) / 2];
108
+ const look = /^<svg[^>]* data-look="([^"]*)"/.exec(floor)?.[1];
109
+ const { title, description } = judgingText(setting, options, true);
110
+ const surfaceLike = { sport: "judo", language, ...(options.theme === undefined ? {} : { theme: options.theme }), ...(options.css === "none" ? { style: "none" } : {}) };
111
+ return {
112
+ id,
113
+ language,
114
+ options: surfaceLike,
115
+ title,
116
+ description: `${description}${language === "ja" ? "" : " "}${words.turn.standing}`,
117
+ standing: words.turn.standing,
118
+ floor,
119
+ viewBox: { x: vx, y: vy, w: vw, h: vh },
120
+ scale,
121
+ uprights,
122
+ defs: "",
123
+ crowdStyle: "",
124
+ venue: false,
125
+ look: { sport: "judo", look: look ?? "yellow-red" },
126
+ planes,
127
+ box: sceneBox(planes, uprights.area, 0, centre, scale),
128
+ centre,
129
+ view: openingView({ preset: "broadcast", ...settings }, JUDGING_PRESETS),
130
+ turnable: true,
131
+ presets: JUDGING_PRESETS,
132
+ ...(near !== undefined && count > 1 ? { lens: BENCHES_LENS } : {}),
133
+ flat: judgingDrawing(options).svg,
134
+ };
135
+ }
136
+ /**
137
+ * Mount a judging assignment as a 3D view in a container: the same view as `mountKyogijoTurn`'s, with the controls, the pointer, the keys and the auto-turn of every 3D view, over the judo floor with each mat's judges'
138
+ * table, the name cards on it and the mat's name over it. Where the browser cannot turn it, the flat picture with its chart is shown.
139
+ * @example
140
+ * ```ts no-run
141
+ * import { mountJudgingTurn } from "@johnmorrisdotca/kyogijo-stadiums/judging";
142
+ * const view = mountJudgingTurn(document.querySelector("#mat")!, { style: "kata", mats: [{ officials: [{ seat: 1, name: "Aiko Mori", id: "J-1042" }] }] });
143
+ * view.set({ yaw: 40 });
144
+ * ```
145
+ */
146
+ export function mountJudgingTurn(host, options, settings = {}) {
147
+ return mountTurn(host, options, settings, buildJudgingTurn);
148
+ }
149
+ /** A judging assignment as the markup of a 3D view, still until a page's script makes it turn: pure, so a server can make it. */
150
+ export function judgingTurnMarkup(options, settings = {}) {
151
+ return turnMarkupOf(buildJudgingTurn(options, settings), settings);
152
+ }
@@ -0,0 +1,72 @@
1
+ import type { Language } from "./types.ts";
2
+ /** The style a judo area is judged in. `kata` has five judges; `shiai`, the contest, is the next release. */
3
+ export type JudgingStyle = "kata";
4
+ /** One official: the seat, and who sits in it. The seat is its number (`3`, `"3"`), its key (`"judge-3"`) or `"central"` (the middle seat of a kata area). */
5
+ export interface JudgingOfficial {
6
+ seat: string | number;
7
+ /** The name as it is to be read, at most 120 characters; a seat with no name is shown as not assigned. */
8
+ name?: string;
9
+ /** The official's identifier (a licence number, a registry id), shown under the name. At most 64 characters. */
10
+ id?: string | number;
11
+ }
12
+ /** One mat area: its name and its officials. */
13
+ export interface JudgingMat {
14
+ /** The name on the plan: `Mat 1`, `Mat 2` in English and `第1試合場`, `第2試合場` in Japanese when missing. At most 40 characters. */
15
+ label?: string;
16
+ /** Who sits where. A seat that is not named is shown as not assigned. */
17
+ officials?: readonly JudgingOfficial[];
18
+ }
19
+ /** A judging assignment as the drawing asks for it. */
20
+ export interface JudgingOptions {
21
+ /** The style the areas are judged in. */
22
+ style: JudgingStyle;
23
+ /** The mat areas, 1 to 6, in the order they are numbered. */
24
+ mats: readonly JudgingMat[];
25
+ /** Draw only this mat (counting from 1): one tab of a page that has a tab for each. All of them, stacked, when missing. */
26
+ mat?: number;
27
+ /** The chart of mat, seat, name and ID. `auto` (the default) draws it whenever a name or an ID does not fit in its seat, `always` and `never` as they say. */
28
+ chart?: "auto" | "always" | "never";
29
+ /** Where the chart is laid: beside the plan (`landscape`, the default) or under it (`portrait`). */
30
+ orientation?: "landscape" | "portrait";
31
+ /** What the plan shows: the whole `mat` (the default) or the judges' `bench`, close up, so that the names in the seats are as large as they can be. */
32
+ focus?: "mat" | "bench";
33
+ /** The width of the picture's own units, 480 to 2000 (1000 by default); the picture scales to whatever it is shown in, so this only sets how large its type is against its width. */
34
+ width?: number;
35
+ language?: Language;
36
+ theme?: "light" | "dark";
37
+ /** A prefix for the ids. Made from the options when missing. */
38
+ id?: string;
39
+ className?: string;
40
+ title?: string;
41
+ description?: string;
42
+ /** `none` leaves the stylesheet to the page (`KYOGIJO_STYLE` and `KYOGIJO_JUDGING_STYLE`). */
43
+ css?: "embed" | "none";
44
+ }
45
+ /** A seat of a style: where it is in the order, and what it is called. */
46
+ export interface JudgingSeat {
47
+ /** The key: `judge-3`. */
48
+ id: string;
49
+ /** The number, from 1, in the order the seats are drawn (left to right as the plan lies, the shomen at the top). */
50
+ n: number;
51
+ /** `central` for the middle seat of a kata area. */
52
+ role: "judge" | "central";
53
+ label: {
54
+ en: string;
55
+ ja: string;
56
+ };
57
+ }
58
+ /** One seat of one mat with who sits in it. */
59
+ export interface JudgingAssignment {
60
+ seat: JudgingSeat;
61
+ /** Empty when nobody is named. */
62
+ name: string;
63
+ id: string;
64
+ assigned: boolean;
65
+ }
66
+ /** One mat with every seat of the style, in order. */
67
+ export interface JudgingResolved {
68
+ /** From 1. */
69
+ n: number;
70
+ label: string;
71
+ seats: JudgingAssignment[];
72
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import type { Language } from "./types.ts";
2
+ export interface JudgingWords {
3
+ /** The name of a mat when the caller gives none, `{n}` its number. */
4
+ mat: string;
5
+ /** What a style is called on the plan, `{count}` the number of seats. */
6
+ styleName: {
7
+ kata: string;
8
+ };
9
+ /** The chart: its heading, its columns, the word before an ID and what a seat with nobody in it says. */
10
+ chart: {
11
+ heading: string;
12
+ seat: string;
13
+ name: string;
14
+ id: string;
15
+ idLabel: string;
16
+ vacant: string;
17
+ };
18
+ /** The sentences of the title and the description. */
19
+ say: {
20
+ title: string;
21
+ matsOne: string;
22
+ matsMany: string;
23
+ mat: string;
24
+ seat: string;
25
+ seatNoId: string;
26
+ seatVacant: string;
27
+ names: string;
28
+ chartOnly: string;
29
+ join: string;
30
+ };
31
+ /** The table that says the same as the picture. */
32
+ table: {
33
+ caption: string;
34
+ mat: string;
35
+ seat: string;
36
+ name: string;
37
+ id: string;
38
+ };
39
+ /** What the 3D view says of the seats' name cards. */
40
+ turn: {
41
+ standing: string;
42
+ board: string;
43
+ };
44
+ }
45
+ export declare const JUDGING_WORDS: Record<Language, JudgingWords>;
@@ -0,0 +1,40 @@
1
+ export const JUDGING_WORDS = {
2
+ en: {
3
+ mat: "Mat {n}",
4
+ styleName: { kata: "Kata, {count} judges" },
5
+ chart: { heading: "Judging assignments", seat: "Seat", name: "Name", id: "ID", idLabel: "ID", vacant: "Not assigned" },
6
+ say: {
7
+ title: "Judging assignments: {style}, {mats}",
8
+ matsOne: "1 mat",
9
+ matsMany: "{n} mats",
10
+ mat: "{label}: {seats}.",
11
+ seat: "{seat} {name}, ID {id}",
12
+ seatNoId: "{seat} {name}",
13
+ seatVacant: "{seat} not assigned",
14
+ names: "The names are in the seats where they fit, and in the chart of mat, seat, name and ID.",
15
+ chartOnly: "The names are in the chart of mat, seat, name and ID.",
16
+ join: "; ",
17
+ },
18
+ table: { caption: "{title}", mat: "Mat", seat: "Seat", name: "Name", id: "ID" },
19
+ turn: { standing: "A name card stands at each judge's seat, turned to face the eye, and the name of each mat stands over its judges' table.", board: "Mat" },
20
+ },
21
+ ja: {
22
+ mat: "第{n}試合場",
23
+ styleName: { kata: "形({count}人制)" },
24
+ chart: { heading: "審判員の配置", seat: "席", name: "氏名", id: "ID", idLabel: "ID", vacant: "未定" },
25
+ say: {
26
+ title: "審判員の配置:{style}、{mats}",
27
+ matsOne: "試合場1面",
28
+ matsMany: "試合場{n}面",
29
+ mat: "{label}:{seats}。",
30
+ seat: "{seat}は{name}(ID {id})",
31
+ seatNoId: "{seat}は{name}",
32
+ seatVacant: "{seat}は未定",
33
+ names: "氏名は、席に収まる場合は各席の中に表示し、試合場・席・氏名・IDの一覧表にも載せています。",
34
+ chartOnly: "氏名は、試合場・席・氏名・IDの一覧表に載せています。",
35
+ join: "、",
36
+ },
37
+ table: { caption: "{title}", mat: "試合場", seat: "席", name: "氏名", id: "ID" },
38
+ turn: { standing: "各審判員の席に名札を置き、見ている方向に向くようにしています。各試合場の名前は審判員席の上に掲げています。", board: "試合場" },
39
+ },
40
+ };
@@ -7,7 +7,7 @@ import { multiDrawing } from "./multi.draw.js";
7
7
  import { outlineOf } from "./outline.js";
8
8
  import { hash } from "./format.js";
9
9
  import { TURN_PRESETS, NET_PRESETS, wrap } from "./turn.camera.js";
10
- import { flat, n, plane, shy, thinnest, wall } from "./turn.kit.js";
10
+ import { box, flat, n, plane, rect, shy, thinnest, wall } from "./turn.kit.js";
11
11
  import { mountTurn } from "./turn.dom.js";
12
12
  import { turnMarkupOf } from "./turn.markup.js";
13
13
  import { openingView, sceneBox } from "./turn.model.js";
@@ -51,14 +51,6 @@ function place(p, m, id) {
51
51
  }
52
52
  return moved;
53
53
  }
54
- const rect = (x, y, w, h) => ({ x0: x, y0: y, x1: x + w, y1: y + h });
55
- /** A solid box on the floor: four sides up to `z1` and a flat top. `cls` paints the top; the sides are one tone darker (`cls-side`). */
56
- function box(id, kind, r, z1, cls) {
57
- const sides = [[r.x0, r.y0, r.x1, r.y0], [r.x1, r.y0, r.x1, r.y1], [r.x1, r.y1, r.x0, r.y1], [r.x0, r.y1, r.x0, r.y0]];
58
- const planes = sides.map(([a, b, c, d], i) => wall(`${id}-${i + 1}`, kind, a, b, c, d, 0, z1, `<rect width="${n(Math.hypot(c - a, d - b))}" height="${n(z1)}" class="${cls}-side"/>`));
59
- planes.push(flat(`${id}-top`, kind, r.x0, r.y0, r.x1, r.y1, z1, `<rect width="${n(r.x1 - r.x0)}" height="${n(r.y1 - r.y0)}" class="${cls}"/>`));
60
- return planes;
61
- }
62
54
  /** The surround of one area: four sides over the rectangle, each in pieces of at most `SURROUND_PIECE`, facing out from the area's middle (it gives way to a nearly level eye, as the glass of a rink does, but only then). */
63
55
  function surround(id, r) {
64
56
  const centre = [(r.x0 + r.x1) / 2, (r.y0 + r.y1) / 2];
package/dist/strings.d.ts CHANGED
@@ -993,7 +993,7 @@ export interface KyogijoWords {
993
993
  item: string;
994
994
  itemSag: string;
995
995
  /** The names of what stands on the surface, by kind. */
996
- parts: Record<"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", string>;
996
+ parts: Record<"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" | "judges", string>;
997
997
  };
998
998
  /** The names of the zones that `zoneAt` answers with (rules.ts), by id. */
999
999
  rules: Record<RuleId, string>;
package/dist/strings.js CHANGED
@@ -432,7 +432,7 @@ export const KYOGIJO_STRINGS = {
432
432
  announce: "{name} view", announceAngle: "Turned to {yaw} degrees, seen from {pitch} degrees above the floor.",
433
433
  flat: "This browser cannot draw the 3D view, so the flat drawing is shown.",
434
434
  standing: "Standing on it, in the 3D view: {list}.", item: "{name} {h}", itemSag: "{name} {h} at the posts, {low} at the middle",
435
- parts: { scoreboard: "scoreboard (lower edge)", net: "net", antennae: "antennae", goalposts: "goal posts", netposts: "net posts", hoop: "baskets", goal: "goals", boards: "boards and glass", stumps: "stumps", pylons: "pylons", flags: "corner flags", fence: "fence", backstop: "backstop", "foul-poles": "foul poles", "table-legs": "table top", walls: "walls", "starting-blocks": "starting blocks", "backstroke-flags": "backstroke flags", "false-start-rope": "false start rope", springboards: "springboards", platforms: "platforms", "judges-stand": "judges' stand", hurdles: "hurdles", steeplechase: "steeplechase barriers", "high-jump": "high jump uprights and bar", "pole-vault": "pole vault uprights and bar", cage: "discus and hammer cage", padding: "padding", "rink-boards": "boards", mound: "clay mound", azuchi: "azuchi", targets: "targets", stands: "stands", "wall-seats": "seats on top of the wall", roof: "roof", gallery: "gallery", dais: "dais", banner: "banner", shrine: "shrine", "mat-edge": "mat thickness", surrounds: "surrounds", officials: "umpires' chairs and score tables" },
435
+ parts: { scoreboard: "scoreboard (lower edge)", net: "net", antennae: "antennae", goalposts: "goal posts", netposts: "net posts", hoop: "baskets", goal: "goals", boards: "boards and glass", stumps: "stumps", pylons: "pylons", flags: "corner flags", fence: "fence", backstop: "backstop", "foul-poles": "foul poles", "table-legs": "table top", walls: "walls", "starting-blocks": "starting blocks", "backstroke-flags": "backstroke flags", "false-start-rope": "false start rope", springboards: "springboards", platforms: "platforms", "judges-stand": "judges' stand", hurdles: "hurdles", steeplechase: "steeplechase barriers", "high-jump": "high jump uprights and bar", "pole-vault": "pole vault uprights and bar", cage: "discus and hammer cage", padding: "padding", "rink-boards": "boards", mound: "clay mound", azuchi: "azuchi", targets: "targets", stands: "stands", "wall-seats": "seats on top of the wall", roof: "roof", gallery: "gallery", dais: "dais", banner: "banner", shrine: "shrine", "mat-edge": "mat thickness", surrounds: "surrounds", officials: "umpires' chairs and score tables", judges: "name cards on the judges' table" },
436
436
  },
437
437
  rules: {
438
438
  "defensive-zone": "Defensive zone",
@@ -968,7 +968,7 @@ export const KYOGIJO_STRINGS = {
968
968
  announce: "{name}の視点", announceAngle: "向きは{yaw}度、見下ろす角度は{pitch}度です。",
969
969
  flat: "このブラウザーでは3D表示ができないため、平面の図を表示しています。",
970
970
  standing: "3D表示で立って見えるもの:{list}。", item: "{name} {h}", itemSag: "{name} 支柱の位置で{h}(中央は{low})",
971
- parts: { 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: "審判席と記録台" },
971
+ parts: { 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: "審判席と記録台", judges: "審判員席の名札" },
972
972
  },
973
973
  rules: {
974
974
  "defensive-zone": "ディフェンディングゾーン",
@@ -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" | "surround" | "chair" | "desk";
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" | "card" | "sign";
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`. */
@@ -32,7 +32,7 @@ export interface Plane {
32
32
  fadeAt?: number;
33
33
  }
34
34
  /** What stands on the surface as a thing a reader would name: a net, a basket, a goal, a pair of posts. */
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";
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" | "judges";
36
36
  /** A thing that stands on the surface: where, how high, and the planes it is made of. */
37
37
  export interface Upright {
38
38
  id: string;
@@ -93,3 +93,14 @@ export declare function shy(p: Plane, centre: readonly [number, number]): Plane;
93
93
  * from straight above hides nothing. `yaw` and `pitch` are the view's, in degrees.
94
94
  */
95
95
  export declare function fadeOf(p: Pick<Plane, "shy" | "fadeTo" | "fadeAt">, yaw: number, pitch: number): number;
96
+ /** A rectangle on the surface, in feet: its corners. */
97
+ export type Rect = {
98
+ x0: number;
99
+ y0: number;
100
+ x1: number;
101
+ y1: number;
102
+ };
103
+ /** A rectangle from its corner and its size. */
104
+ export declare const rect: (x: number, y: number, w: number, h: number) => Rect;
105
+ /** A solid box on the floor: four sides up to `z1` and a flat top. `cls` paints the top; the sides are one tone darker (`cls-side`). */
106
+ export declare function box(id: string, kind: Plane["kind"], r: Rect, z1: number, cls: string): Plane[];
package/dist/turn.kit.js CHANGED
@@ -109,3 +109,12 @@ export function fadeOf(p, yaw, pitch) {
109
109
  const t = Math.min(1, Math.max(0, (hides - from) / 0.25));
110
110
  return Math.round((1 - (1 - (p.fadeTo ?? 0.08)) * t) * 1000) / 1000;
111
111
  }
112
+ /** A rectangle from its corner and its size. */
113
+ export const rect = (x, y, w, h) => ({ x0: x, y0: y, x1: x + w, y1: y + h });
114
+ /** A solid box on the floor: four sides up to `z1` and a flat top. `cls` paints the top; the sides are one tone darker (`cls-side`). */
115
+ export function box(id, kind, r, z1, cls) {
116
+ const sides = [[r.x0, r.y0, r.x1, r.y0], [r.x1, r.y0, r.x1, r.y1], [r.x1, r.y1, r.x0, r.y1], [r.x0, r.y1, r.x0, r.y0]];
117
+ const planes = sides.map(([a, b, c, d], i) => wall(`${id}-${i + 1}`, kind, a, b, c, d, 0, z1, `<rect width="${n(Math.hypot(c - a, d - b))}" height="${n(z1)}" class="${cls}-side"/>`));
118
+ planes.push(flat(`${id}-top`, kind, r.x0, r.y0, r.x1, r.y1, z1, `<rect width="${n(r.x1 - r.x0)}" height="${n(r.y1 - r.y0)}" class="${cls}"/>`));
119
+ return planes;
120
+ }
@@ -20,7 +20,7 @@ export function planeMatrix(p, scale, centre = [0, 0]) {
20
20
  }
21
21
  /** Work a view out in a container `width` by `height` pixels. */
22
22
  export function layoutOf(model, view, width, height, lens) {
23
- const distance = eyeDistance(width, height, lens);
23
+ const distance = eyeDistance(width, height, lens ?? model.lens);
24
24
  const at = fit(model.box, { ...view, zoom: 1 }, width, height, distance);
25
25
  const camera = cameraMatrix(view, at, distance);
26
26
  const s = model.scale;
@@ -95,6 +95,8 @@ export interface TurnModel {
95
95
  turnable: boolean;
96
96
  /** The four viewpoints, when the model has its own (a tournament layout's); the sport's when missing. */
97
97
  presets?: PresetTable;
98
+ /** A lens of the model's own, as a multiple of the view's longer side, used when the settings give none: a scene that turns about a point far from some of what it shows wants a longer one, so that what swings toward the eye does not swell. */
99
+ lens?: number;
98
100
  /** 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
101
  flat?: string;
100
102
  }
@@ -1,11 +1,12 @@
1
+ import { JUDGING_DARK, JUDGING_LIGHT } from "./judging.style.js";
1
2
  /**
2
3
  * The 3D view's stylesheet: the colours of what stands on the surface, in light and dark, and the rules that lay the scene out. Like the drawing's own, the
3
4
  * defaults are in `:where()`, which has no weight, so a page's own rule always wins; the colours are custom properties (`--ky-up-*`) a page can set.
4
5
  */
5
6
  export const KYOGIJO_TURN_STYLE = `
6
- :where(.ky-turn){--ky-left:#2f6db0;--ky-right:#b5452c;--ky-cr-home:#b5452c;--ky-cr-away:#2f6db0;--ky-cr-bed:#4a525d;--ky-cr-empty:#7d8794;--ky-cr-gap:#14181d;--ky-cr-conc:#8c929b;--ky-cr-dim:1;--ky-up-roof:rgba(214,218,212,.6);--ky-up-roofline:rgba(40,46,42,.55);--ky-up-fascia:#9aa09a;--ky-up-door:#8a6a46;--ky-up-window:rgba(150,200,240,.55);--ky-up-sash:#f2f0e6;--ky-up-banner:#26211d;--ky-up-bannerline:#c8b88a;--ky-up-room:#efe8d6;--ky-up-roomback:#d6cdb9;--ky-up-ceiling:#dedcd5;--ky-up-railw:#fbfaf6;--ky-up-wainline:#6b4a2a;--ky-up-riser:#3f342c;--ky-up-slab:#d8cfba;--ky-up-slabedge:#c2b69c;--ky-up-seatc:#46342a;--ky-up-seatb:#2d211b;--ky-up-dais:#a0683a;--ky-up-dais-side:#6a4326;--ky-up-shrine:#c9b48f;--ky-up-shrine-side:#8f7a55;--ky-up-gal:#4d4037;--ky-up-aisle:#dcc196;--ky-up-panel:rgba(95,58,22,.3);--ky-up-alcove:#b58a52;--ky-up-purple:#5b2a6e;--ky-up-clock:#ffffff;--ky-up-rail:#3a2c1f;--ky-up-matedge:#2a2a26;--ky-up-sbframe:#1a1f1c;--ky-up-sbedge:#5b645e;--ky-up-sbline:#59615b;--ky-up-pole:#e9e7de;--ky-up-white:#f6f4ec;--ky-up-yellow:#f2c230;--ky-up-red:#c8372d;--ky-up-orange:#f26a1b;--ky-up-wood:#e3d4b4;--ky-up-leg:#474e49;--ky-up-pad:#2f6db0;--ky-up-table:#18243f;--ky-up-foot:rgba(0,0,0,.25);--ky-up-ground:#d9d5c7;--ky-up-wall:#2f4a36;--ky-up-clay:#a8703f;--ky-up-cap:#1d2a22;--ky-up-board:#ece9df;--ky-up-glass:rgba(138,190,232,.26);--ky-up-backboard:rgba(255,255,255,.88);--ky-up-outline:#f2f0e6;--ky-up-rim:#e8591a;--ky-up-mesh:rgba(24,30,27,.7);--ky-up-netfill:rgba(255,255,255,.2);--ky-up-tape:#fbfaf4;--ky-turn-bg:transparent;--ky-turn-ink:#1f2320;--ky-turn-rule:rgba(31,35,32,.22);--ky-turn-chip:rgba(255,255,255,.86);--ky-turn-chip-on:#1f2320;--ky-turn-chip-on-ink:#fff}
7
- @media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-turn:not([data-theme=light])){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320}}
8
- :where(html[data-theme=dark] .ky-turn,.ky-turn[data-theme=dark]){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320}
7
+ :where(.ky-turn){--ky-left:#2f6db0;--ky-right:#b5452c;--ky-cr-home:#b5452c;--ky-cr-away:#2f6db0;--ky-cr-bed:#4a525d;--ky-cr-empty:#7d8794;--ky-cr-gap:#14181d;--ky-cr-conc:#8c929b;--ky-cr-dim:1;--ky-up-roof:rgba(214,218,212,.6);--ky-up-roofline:rgba(40,46,42,.55);--ky-up-fascia:#9aa09a;--ky-up-door:#8a6a46;--ky-up-window:rgba(150,200,240,.55);--ky-up-sash:#f2f0e6;--ky-up-banner:#26211d;--ky-up-bannerline:#c8b88a;--ky-up-room:#efe8d6;--ky-up-roomback:#d6cdb9;--ky-up-ceiling:#dedcd5;--ky-up-railw:#fbfaf6;--ky-up-wainline:#6b4a2a;--ky-up-riser:#3f342c;--ky-up-slab:#d8cfba;--ky-up-slabedge:#c2b69c;--ky-up-seatc:#46342a;--ky-up-seatb:#2d211b;--ky-up-dais:#a0683a;--ky-up-dais-side:#6a4326;--ky-up-shrine:#c9b48f;--ky-up-shrine-side:#8f7a55;--ky-up-gal:#4d4037;--ky-up-aisle:#dcc196;--ky-up-panel:rgba(95,58,22,.3);--ky-up-alcove:#b58a52;--ky-up-purple:#5b2a6e;--ky-up-clock:#ffffff;--ky-up-rail:#3a2c1f;--ky-up-matedge:#2a2a26;--ky-up-sbframe:#1a1f1c;--ky-up-sbedge:#5b645e;--ky-up-sbline:#59615b;--ky-up-pole:#e9e7de;--ky-up-white:#f6f4ec;--ky-up-yellow:#f2c230;--ky-up-red:#c8372d;--ky-up-orange:#f26a1b;--ky-up-wood:#e3d4b4;--ky-up-leg:#474e49;--ky-up-pad:#2f6db0;--ky-up-table:#18243f;--ky-up-foot:rgba(0,0,0,.25);--ky-up-ground:#d9d5c7;--ky-up-wall:#2f4a36;--ky-up-clay:#a8703f;--ky-up-cap:#1d2a22;--ky-up-board:#ece9df;--ky-up-glass:rgba(138,190,232,.26);--ky-up-backboard:rgba(255,255,255,.88);--ky-up-outline:#f2f0e6;--ky-up-rim:#e8591a;--ky-up-mesh:rgba(24,30,27,.7);--ky-up-netfill:rgba(255,255,255,.2);--ky-up-tape:#fbfaf4;--ky-turn-bg:transparent;--ky-turn-ink:#1f2320;--ky-turn-rule:rgba(31,35,32,.22);--ky-turn-chip:rgba(255,255,255,.86);--ky-turn-chip-on:#1f2320;--ky-turn-chip-on-ink:#fff;${JUDGING_LIGHT}}
8
+ @media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-turn:not([data-theme=light])){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK}}}
9
+ :where(html[data-theme=dark] .ky-turn,.ky-turn[data-theme=dark]){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK}}
9
10
  .ky-turn{position:relative;display:block;width:100%;color:var(--ky-turn-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);background:var(--ky-turn-bg)}
10
11
  .ky-turn-view{position:relative;isolation:isolate;width:100%;aspect-ratio:var(--ky-turn-ratio,16/10);min-height:260px;overflow:hidden;touch-action:pan-y;cursor:grab;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;border-radius:12px;outline-offset:2px}
11
12
  .ky-turn[data-touch=turn] .ky-turn-view{touch-action:none}
@@ -103,6 +104,17 @@ export const KYOGIJO_TURN_STYLE = `
103
104
  .ky-turn .u-surround-rim{fill:var(--ky-mu-chair,#ece7da);fill-opacity:.55}
104
105
  .ky-turn .u-chair{fill:var(--ky-mu-chair,#ece7da);stroke:var(--ky-mu-chair-edge,#4a4f56);stroke-width:.06}
105
106
  .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}
107
+ .ky-turn .u-bench{fill:var(--ky-ju-desk);stroke:var(--ky-ju-desk-edge);stroke-width:.05}
108
+ .ky-turn .u-bench-side{fill:color-mix(in srgb,var(--ky-ju-desk) 62%,#000);stroke:var(--ky-ju-desk-edge);stroke-width:.04}
109
+ .ky-turn .u-card{fill:var(--ky-ju-card);stroke:var(--ky-ju-card-edge);stroke-width:.06}
110
+ .ky-turn .u-card-name{fill:var(--ky-ju-card-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);font-weight:800;text-anchor:middle}
111
+ .ky-turn .u-card-id{fill:var(--ky-ju-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-weight:700;text-anchor:middle;font-variant-numeric:tabular-nums}
112
+ .ky-turn .u-card-badge{fill:var(--ky-ju-badge);stroke:var(--ky-ju-card);stroke-width:.06}
113
+ .ky-turn .u-card-badge-empty{fill:var(--ky-ju-card);stroke:var(--ky-ju-vacant);stroke-dasharray:.2 .14}
114
+ .ky-turn .u-card-badge-empty+.u-card-n{fill:var(--ky-ju-vacant)}
115
+ .ky-turn .u-card-n{fill:var(--ky-ju-badge-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);font-weight:800;text-anchor:middle}
116
+ .ky-turn .u-sign{fill:var(--ky-ju-badge);stroke:var(--ky-ju-card);stroke-width:.05}
117
+ .ky-turn .u-sign-text{fill:var(--ky-ju-badge-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);font-weight:800;text-anchor:middle}
106
118
  .ky-turn-controls{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;align-items:center}
107
119
  .ky-turn-group{display:flex;flex-wrap:wrap;gap:6px}
108
120
  .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/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.16.1";
2
+ export declare const VERSION = "0.17.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.16.1";
2
+ export const VERSION = "0.17.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/kyogijo-stadiums",
3
- "version": "0.16.1",
3
+ "version": "0.17.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",
@@ -156,6 +156,11 @@
156
156
  "import": "./dist/budo-entry.js",
157
157
  "default": "./dist/budo-entry.js"
158
158
  },
159
+ "./judging": {
160
+ "types": "./dist/judging-entry.d.ts",
161
+ "import": "./dist/judging-entry.js",
162
+ "default": "./dist/judging-entry.js"
163
+ },
159
164
  "./charts": {
160
165
  "types": "./dist/charts-entry.d.ts",
161
166
  "import": "./dist/charts-entry.js",