@johnmorrisdotca/kyogijo-stadiums 0.16.1 → 0.18.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,30 @@
1
+ // The rules of a judging assignment, light and dark: the picture's ground and ink, the chart's rows, the badge of a seat's number and the name card in a seat. Everything is high contrast on purpose: it is read live, on a tablet,
2
+ // at arm's length. The surface keeps its own rules, which the drawing carries as it always does.
3
+ /** The colours of a judging picture in light and in dark, as custom properties (the 3D view's stylesheet carries them too, for its name cards). */
4
+ export const JUDGING_LIGHT = "--ky-ju-bg:#f7f5ef;--ky-ju-ink:#10151b;--ky-ju-muted:#434b54;--ky-ju-row:#ffffff;--ky-ju-rule:#b9b3a3;--ky-ju-badge:#10151b;--ky-ju-badge-ink:#ffffff;--ky-ju-card:#ffffff;--ky-ju-card-ink:#10151b;--ky-ju-card-edge:#10151b;--ky-ju-vacant:#6a717a;--ky-ju-desk:#e9e2d0;--ky-ju-desk-edge:#4a4f56";
5
+ export const JUDGING_DARK = "--ky-ju-bg:#0d1218;--ky-ju-ink:#f6f3ea;--ky-ju-muted:#bcc3cb;--ky-ju-row:#18212a;--ky-ju-rule:#475463;--ky-ju-badge:#f6f3ea;--ky-ju-badge-ink:#0d1218;--ky-ju-card:#18212a;--ky-ju-card-ink:#f6f3ea;--ky-ju-card-edge:#f6f3ea;--ky-ju-vacant:#98a1ab;--ky-ju-desk:#6f6859;--ky-ju-desk-edge:#05070a";
6
+ /**
7
+ * The rules of a judging assignment. `:where` keeps the specificity at nothing so that a page's own custom properties win; the picture follows the device and the page like a drawing does, and a `data-theme` of light or
8
+ * dark forces it. The colours are custom properties (`--ky-ju-*`) a page can set.
9
+ */
10
+ export const KYOGIJO_JUDGING_STYLE = `
11
+ :where(.kyogijo){${JUDGING_LIGHT}}
12
+ @media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .kyogijo:not([data-theme=light])){${JUDGING_DARK}}}
13
+ :where(html[data-theme=dark] .kyogijo,.kyogijo[data-theme=dark]){${JUDGING_DARK}}
14
+ .kyogijo .ky-ju-ground{fill:var(--ky-ju-bg)}
15
+ .kyogijo .ky-ju-mat{fill:var(--ky-ju-ink);font-family:var(--ky-font);font-weight:800}
16
+ .kyogijo .ky-ju-style{fill:var(--ky-ju-muted);font-family:var(--ky-font);font-weight:700}
17
+ .kyogijo .ky-ju-row{fill:var(--ky-ju-row);stroke:var(--ky-ju-rule);stroke-width:1.5}
18
+ .kyogijo .ky-ju-role{fill:var(--ky-ju-muted);font-family:var(--ky-font);font-weight:700}
19
+ .kyogijo .ky-ju-name{fill:var(--ky-ju-ink);font-family:var(--ky-font);font-weight:800}
20
+ .kyogijo .ky-ju-id{fill:var(--ky-ju-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-weight:700;font-variant-numeric:tabular-nums}
21
+ .kyogijo .ky-ju-vacant{fill:var(--ky-ju-vacant);font-family:var(--ky-font);font-weight:700;font-style:italic}
22
+ .kyogijo .ky-ju-badge{fill:var(--ky-ju-badge);stroke:var(--ky-ju-bg);stroke-width:2}
23
+ .kyogijo .ky-ju-badge-empty{fill:var(--ky-ju-bg);stroke:var(--ky-ju-vacant);stroke-width:2;stroke-dasharray:4 3}
24
+ .kyogijo .ky-ju-badge-n{fill:var(--ky-ju-badge-ink);font-family:var(--ky-font);font-weight:800;text-anchor:middle}
25
+ .kyogijo .ky-ju-badge-empty+.ky-ju-badge-n{fill:var(--ky-ju-vacant)}
26
+ .kyogijo .ky-ju-card{fill:var(--ky-ju-card);stroke:var(--ky-ju-card-edge);stroke-width:1.4}
27
+ .kyogijo .ky-ju-card-name{fill:var(--ky-ju-card-ink);font-family:var(--ky-font);font-weight:800;text-anchor:middle}
28
+ .kyogijo .ky-ju-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}
29
+ .kyogijo .ky-ju-plan-frame{fill:none;stroke:var(--ky-ju-rule);stroke-width:1.5}
30
+ `.trim();
@@ -0,0 +1,36 @@
1
+ import type { JudgingOptions } from "./judging.types.ts";
2
+ import { type KyogijoTurn } from "./turn.dom.ts";
3
+ import { type PresetTable } from "./turn.camera.ts";
4
+ import { type TurnModel, type TurnSettings } from "./turn.model.ts";
5
+ /** 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. */
6
+ export declare const JUDGING_HEIGHTS: Readonly<{
7
+ table: 0.75;
8
+ chair: 0.45;
9
+ card: 0.95;
10
+ sign: 0.7;
11
+ signAbove: 0.25;
12
+ post: 1;
13
+ }>;
14
+ /**
15
+ * 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
16
+ * 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.
17
+ */
18
+ export declare const JUDGING_PRESETS: PresetTable;
19
+ /**
20
+ * 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.
21
+ * 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.
22
+ */
23
+ export declare function buildJudgingTurn(options: JudgingOptions, settings?: TurnSettings): TurnModel;
24
+ /**
25
+ * 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'
26
+ * 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.
27
+ * @example
28
+ * ```ts no-run
29
+ * import { mountJudgingTurn } from "@johnmorrisdotca/kyogijo-stadiums/judging";
30
+ * const view = mountJudgingTurn(document.querySelector("#mat")!, { style: "kata", mats: [{ officials: [{ seat: 1, name: "Aiko Mori", id: "J-1042" }] }] });
31
+ * view.set({ yaw: 40 });
32
+ * ```
33
+ */
34
+ export declare function mountJudgingTurn(host: HTMLElement, options: JudgingOptions, settings?: TurnSettings): KyogijoTurn<JudgingOptions>;
35
+ /** 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. */
36
+ export declare function judgingTurnMarkup(options: JudgingOptions, settings?: TurnSettings): string;
@@ -0,0 +1,169 @@
1
+ import { drawKyogijo } from "./draw.js";
2
+ import { hash } from "./format.js";
3
+ import { FEET_PER_METRE as FT } from "./geometry.js";
4
+ import { TURN_CARD, badge, card } from "./judging.card.js";
5
+ import { JUDGING_LEAGUE } from "./judging.data.js";
6
+ import { fitCard, fitSeats } from "./judging.fit.js";
7
+ import { benchOf, judgingPlan, judgingSetting, seatIn } from "./judging.js";
8
+ import { judgingDrawing, judgingText } from "./judging.draw.js";
9
+ import { JUDGING_WORDS } from "./judging.words.js";
10
+ import { mountTurn } from "./turn.dom.js";
11
+ import { turnMarkupOf } from "./turn.markup.js";
12
+ import { TURN_PRESETS } from "./turn.camera.js";
13
+ import { WORLD_PX, openingView, sceneBox } from "./turn.model.js";
14
+ import { box, n, plane, pole, rect, thinnest } from "./turn.kit.js";
15
+ import { planesOf } from "./uprights.js";
16
+ /** 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. */
17
+ export const JUDGING_HEIGHTS = Object.freeze({ table: 0.75, chair: 0.45, card: 0.95, sign: 0.7, signAbove: 0.25, post: 1.0 });
18
+ /** 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). */
19
+ const BENCHES_LENS = 60;
20
+ /** The bench the 3D view is fitted to when it is close up: a kata's seats at their tables, a contest's technical tables and referees, all mats in one box, reaching as little of the floor in front as will do. */
21
+ function benchNear(style, geom, count) {
22
+ if (style === "shiai") {
23
+ const b = geom.mats.slice(0, count).map((m) => m.bench);
24
+ return { x0: Math.min(...b.map((x) => x.x0)), y0: Math.min(...b.map((x) => x.y0)), x1: Math.max(...b.map((x) => x.x1)), y1: Math.max(...b.map((x) => x.y1)) };
25
+ }
26
+ const chairs = geom.mats.slice(0, count).flatMap((m) => m.places.map((p) => p.chair));
27
+ return benchOf(chairs, geom.table, geom.pitch, geom.plan.judo.contests[0].y0, 2.2);
28
+ }
29
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
30
+ /**
31
+ * 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
32
+ * 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.
33
+ */
34
+ 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 } };
35
+ /**
36
+ * 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.
37
+ * 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.
38
+ */
39
+ export function buildJudgingTurn(options, settings = {}) {
40
+ const setting = judgingSetting(options);
41
+ const language = setting.language;
42
+ const words = JUDGING_WORDS[language];
43
+ const league = JUDGING_LEAGUE[setting.style].league;
44
+ const count = setting.mats.length;
45
+ const id = settings.id ?? (typeof options.id === "string" && options.id !== "" ? `${options.id}-turn` : `ky-turn-${hash(JSON.stringify([options, settings.preset]))}`);
46
+ 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" } : {}) };
47
+ const floor = drawKyogijo({ ...floorOptions, orientation: "landscape" });
48
+ const found = /viewBox="(-?[\d.]+) (-?[\d.]+) ([\d.]+) ([\d.]+)"/.exec(floor);
49
+ if (found === null)
50
+ throw new Error("the floor of a 3D view must be an SVG with a viewBox");
51
+ const [vx, vy, vw, vh] = found.slice(1).map(Number);
52
+ const geom = judgingPlan(setting.style, count);
53
+ // The plan's own frame is metres from the top left of the tatami, and the drawing's is feet from the same corner.
54
+ const ft = (m) => m * FT;
55
+ const H = JUDGING_HEIGHTS;
56
+ const items = [];
57
+ const tableTop = ft(H.table);
58
+ const cardH = ft(H.card);
59
+ const minName = 0.62;
60
+ // Close up on the bench, only the seats at it stand (the referee and the side judges of a contest are out of the picture, and in the chart).
61
+ const near = setting.focus === "bench" ? benchNear(setting.style, geom, count) : undefined;
62
+ setting.mats.forEach((mat, mi) => {
63
+ const places = geom.mats[mi];
64
+ const table = places.table;
65
+ const boxes = mat.seats.map((a) => {
66
+ const c = places.places[a.seat.n - 1].card;
67
+ const w = ft(c.x1 - c.x0);
68
+ const pad = Math.max(0.08, w * 0.05);
69
+ const r = clamp(w * 0.2, 0.2, 0.45);
70
+ return { w, pad, r, width: w - 2 * pad, height: cardH - 2 * pad - r, max: Math.min(w * 0.3, 0.75) };
71
+ });
72
+ const fitted = fitSeats(mat.seats, boxes, { min: minName, idLabel: words.chart.idLabel });
73
+ const furniture = [];
74
+ 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"));
75
+ const cards = [];
76
+ const stands = [];
77
+ mat.seats.forEach((a, si) => {
78
+ const place = places.places[a.seat.n - 1];
79
+ if (near !== undefined && !seatIn(place, near))
80
+ return;
81
+ const b = boxes[si];
82
+ // A chair for the seats that have one (the referee stands on the mat).
83
+ if (a.seat.role !== "referee")
84
+ furniture.push(...box(`m${mat.n}-chair-${a.seat.n}`, "chair", rect(ft(place.chair.x0), ft(place.chair.y0), ft(place.chair.x1 - place.chair.x0), ft(place.chair.y1 - place.chair.y0)), ft(H.chair), "u-bench"));
85
+ const cx = ft((place.card.x0 + place.card.x1) / 2);
86
+ // A card at a table stands on the table; any other on a post, at the height of a standing official's chest.
87
+ const atTable = a.seat.where === "table";
88
+ const cy = atTable ? ft((table.y0 + table.y1) / 2) : ft((place.card.y0 + place.card.y1) / 2);
89
+ const base = atTable ? tableTop : ft(H.post);
90
+ if (!atTable)
91
+ stands.push(...pole(`m${mat.n}-post-${a.seat.n}`, "pole", cx, cy, 0, base, ft(0.06), "u-pole"));
92
+ const fit = fitted[si];
93
+ if (fit !== undefined) {
94
+ const p = plane(`m${mat.n}-card-${a.seat.n}`, "card", [cx - b.w / 2, cy, base + cardH], [b.w, 0, 0], [0, 0, -cardH], card(TURN_CARD, fit, a.seat.n, 0, 0, b.w, cardH, b.pad, b.r, 0.1));
95
+ cards.push({ ...p, billboard: true });
96
+ }
97
+ else {
98
+ // 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.
99
+ const br = 0.55;
100
+ const p = plane(`m${mat.n}-badge-${a.seat.n}`, "card", [cx - br, cy, base + 2 * br], [2 * br, 0, 0], [0, 0, -2 * br], badge(TURN_CARD, br, br, br * 0.92, a.seat.n, a.assigned));
101
+ cards.push({ ...p, billboard: true });
102
+ }
103
+ });
104
+ // The mat's name on a sign over the whole table, on a pole at each end of it.
105
+ const midX = ft((table.x0 + table.x1) / 2);
106
+ const midY = ft((table.y0 + table.y1) / 2);
107
+ const signW = ft(table.x1 - table.x0);
108
+ const signH = ft(H.sign);
109
+ const signBottom = tableTop + cardH + ft(H.signAbove);
110
+ 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 });
111
+ const lines = text?.name ?? [mat.label];
112
+ const size = text?.nameSize ?? signH * 0.3;
113
+ 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("");
114
+ 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 };
115
+ 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"));
116
+ items.push({ id: `m${mat.n}-furniture`, kind: "officials", at: [midX, midY], height: tableTop, planes: furniture });
117
+ items.push({ id: `m${mat.n}-judges`, kind: "judges", at: [midX, midY], height: tableTop + cardH, planes: [...cards, ...stands, ...poles, sign] });
118
+ });
119
+ // The view is fitted to the whole floor, or to the officials' bench close up (the scene then turns about the bench, so that the cards stay where they are read).
120
+ 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) };
121
+ const uprights = { items, lift: 0, thin: thinnest(vw, vh), area, whole: true };
122
+ const planes = planesOf(uprights);
123
+ const scale = WORLD_PX / Math.max(vw, vh);
124
+ const centre = [(area.x0 + area.x1) / 2, (area.y0 + area.y1) / 2];
125
+ const look = /^<svg[^>]* data-look="([^"]*)"/.exec(floor)?.[1];
126
+ const { title, description } = judgingText(setting, options, true);
127
+ const surfaceLike = { sport: "judo", language, ...(options.theme === undefined ? {} : { theme: options.theme }), ...(options.css === "none" ? { style: "none" } : {}) };
128
+ return {
129
+ id,
130
+ language,
131
+ options: surfaceLike,
132
+ title,
133
+ description: `${description}${language === "ja" ? "" : " "}${words.turn.standing}`,
134
+ standing: words.turn.standing,
135
+ floor,
136
+ viewBox: { x: vx, y: vy, w: vw, h: vh },
137
+ scale,
138
+ uprights,
139
+ defs: "",
140
+ crowdStyle: "",
141
+ venue: false,
142
+ look: { sport: "judo", look: look ?? "yellow-red" },
143
+ planes,
144
+ box: sceneBox(planes, uprights.area, 0, centre, scale),
145
+ centre,
146
+ view: openingView({ preset: "broadcast", ...settings }, JUDGING_PRESETS),
147
+ turnable: true,
148
+ presets: JUDGING_PRESETS,
149
+ ...(near !== undefined && count > 1 ? { lens: BENCHES_LENS } : {}),
150
+ flat: judgingDrawing(options).svg,
151
+ };
152
+ }
153
+ /**
154
+ * 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'
155
+ * 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.
156
+ * @example
157
+ * ```ts no-run
158
+ * import { mountJudgingTurn } from "@johnmorrisdotca/kyogijo-stadiums/judging";
159
+ * const view = mountJudgingTurn(document.querySelector("#mat")!, { style: "kata", mats: [{ officials: [{ seat: 1, name: "Aiko Mori", id: "J-1042" }] }] });
160
+ * view.set({ yaw: 40 });
161
+ * ```
162
+ */
163
+ export function mountJudgingTurn(host, options, settings = {}) {
164
+ return mountTurn(host, options, settings, buildJudgingTurn);
165
+ }
166
+ /** 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. */
167
+ export function judgingTurnMarkup(options, settings = {}) {
168
+ return turnMarkupOf(buildJudgingTurn(options, settings), settings);
169
+ }
@@ -0,0 +1,78 @@
1
+ import type { Language } from "./types.ts";
2
+ /** The style a judo area is officiated in: `kata` has five judges; `shiai`, the contest, a referee on the mat and the officials at the technical table. */
3
+ export type JudgingStyle = "kata" | "shiai";
4
+ /** One official: the seat, and who sits in it. The seat is its number (`3`, `"3"`), its key (`"judge-3"`, `"referee"`, `"timekeeper"`) or a word (`"central"` for 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
+ /** Shiai: draw the two side judges' seats too (the IJF's rules for an event with no CARE system use them; its other events have none), as not assigned where nobody is named. A seat a mat names is drawn whatever this says. Off by default. */
30
+ sideJudges?: boolean;
31
+ /** Where the chart is laid: beside the plan (`landscape`, the default) or under it (`portrait`). */
32
+ orientation?: "landscape" | "portrait";
33
+ /** 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. */
34
+ focus?: "mat" | "bench";
35
+ /** 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. */
36
+ width?: number;
37
+ language?: Language;
38
+ theme?: "light" | "dark";
39
+ /** A prefix for the ids. Made from the options when missing. */
40
+ id?: string;
41
+ className?: string;
42
+ title?: string;
43
+ description?: string;
44
+ /** `none` leaves the stylesheet to the page (`KYOGIJO_STYLE` and `KYOGIJO_JUDGING_STYLE`). */
45
+ css?: "embed" | "none";
46
+ }
47
+ /** A seat of a style: where it is in the order, and what it is called. */
48
+ export interface JudgingSeat {
49
+ /** The key: `judge-3`. */
50
+ id: string;
51
+ /** The number, from 1, in the order the seats are listed (a kata area's from the left as the plan lies, the shomen at the top). */
52
+ n: number;
53
+ /** What the seat is: a kata area's judges (`central` the middle one), or a contest's referee, side judges, timekeeper, scorekeeper and referee supervisor. */
54
+ role: "judge" | "central" | "referee" | "side-judge" | "timekeeper" | "scorekeeper" | "supervisor";
55
+ /** Where it is: at the judges' table of a kata area or the technical table of a contest (`table`), on the mat (`mat`: the referee) or at a corner of the area (`corner`: a side judge). */
56
+ where: "table" | "mat" | "corner";
57
+ /** True for a seat that is drawn only when asked for or named (the side judges). */
58
+ optional?: true;
59
+ label: {
60
+ en: string;
61
+ ja: string;
62
+ };
63
+ }
64
+ /** One seat of one mat with who sits in it. */
65
+ export interface JudgingAssignment {
66
+ seat: JudgingSeat;
67
+ /** Empty when nobody is named. */
68
+ name: string;
69
+ id: string;
70
+ assigned: boolean;
71
+ }
72
+ /** One mat with every seat of the style, in order. */
73
+ export interface JudgingResolved {
74
+ /** From 1. */
75
+ n: number;
76
+ label: string;
77
+ seats: JudgingAssignment[];
78
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
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
+ shiai: string;
9
+ };
10
+ /** The chart: its heading, its columns, the word before an ID and what a seat with nobody in it says. */
11
+ chart: {
12
+ heading: string;
13
+ seat: string;
14
+ name: string;
15
+ id: string;
16
+ idLabel: string;
17
+ vacant: string;
18
+ };
19
+ /** The sentences of the title and the description. */
20
+ say: {
21
+ title: string;
22
+ matsOne: string;
23
+ matsMany: string;
24
+ mat: string;
25
+ seat: string;
26
+ seatNoId: string;
27
+ seatVacant: string;
28
+ names: string;
29
+ chartOnly: string;
30
+ join: string;
31
+ };
32
+ /** The table that says the same as the picture. */
33
+ table: {
34
+ caption: string;
35
+ mat: string;
36
+ seat: string;
37
+ name: string;
38
+ id: string;
39
+ };
40
+ /** What the 3D view says of the seats' name cards. */
41
+ turn: {
42
+ standing: string;
43
+ board: string;
44
+ };
45
+ }
46
+ 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", shiai: "Shiai, {count} officials" },
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}人制)", shiai: "試合({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.18.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.18.0";