@johnmorrisdotca/kyogijo-stadiums 0.43.0 → 0.44.1

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.
@@ -3,7 +3,9 @@
3
3
  // plan drawn with each venue's building only. The model it makes is a `TurnModel`, so the camera, the controls, the pointer, the auto-turn and the steady fit of every other 3D view are this one's; its
4
4
  // buildings are stacked building by building (`Plane.group`), the far one first, so that a roof never shows through a nearer building.
5
5
  import { describeDistrict, districtId, drawDistrict, drawMember, nestedDrawing, planDistrict, surfaceCentre } from "./district.js";
6
- import { blockPlanes, contextUprights, shellBlock } from "./context.js";
6
+ import { blockPlanes, contextUprights, shellBlock, slabPlanes } from "./context.js";
7
+ import { dojo } from "./budo/dojo.api.js";
8
+ import { KODOKAN_FLOORS, KODOKAN_GALLERY_LEVEL } from "./kodokan.js";
7
9
  import { APART, n, plane } from "./turn.kit.js";
8
10
  import { TURN_PRESETS, wrap } from "./turn.camera.js";
9
11
  import { mountTurn } from "./turn.dom.js";
@@ -74,39 +76,83 @@ export function buildDistrictTurn(options, settings = {}) {
74
76
  let crowdStyle = "";
75
77
  plan.members.forEach((placed, i) => {
76
78
  const member = placed.member;
77
- const memberId = `${id}-m${i}`;
78
- // Each venue is built as its own 3D view is, with the crowd drawn without its seats on its floor and the stands raised.
79
- const model = buildTurn({ ...member.options, language, ...(options.theme === undefined ? {} : { theme: options.theme }), id: `${base}-m${i}` }, { ...settings, id: memberId, venue: { roof: true, ...(typeof settings.venue === "object" ? settings.venue : {}) }, preset: undefined, view: undefined });
80
- const centre = surfaceCentre(member.options);
81
79
  const prefix = `m${i}-`;
82
- // A venue on an upper floor (the Kodokan's main dojo) stands that high over the ground, on a floor drawn in the air; the ground under it is left bare.
83
- const lift = placed.member.elevation ?? 0;
84
- for (const item of model.uprights.items) {
85
- const planes = item.planes.filter((p) => p.kind !== "ground").map((p) => placeInDistrict(p, placed, centre, prefix, i, lift));
86
- if (planes.length > 0) {
87
- const [ax, ay] = item.at;
88
- const [rx, ry] = rot([ax - centre[0], ay - centre[1]], placed.angle);
89
- items.push({ ...item, id: `${prefix}${item.id}`, at: [rx + placed.at[0], ry + placed.at[1]], height: item.height + lift, planes });
80
+ // A venue may have several surfaces (the rooms of a floor): each is built as its own 3D view is, with the crowd drawn without its seats on its floor and the stands raised, and put where the plan puts it, in the venue's one group.
81
+ placed.parts.forEach((pp, j) => {
82
+ const part = pp.part;
83
+ const tag = j === 0 ? "" : `p${j}`;
84
+ const memberId = `${id}-m${i}${tag}`;
85
+ const model = buildTurn({ ...part.options, language, ...(options.theme === undefined ? {} : { theme: options.theme }), id: `${base}-m${i}${tag}` }, { ...settings, id: memberId, venue: { roof: true, ...(typeof settings.venue === "object" ? settings.venue : {}) }, preset: undefined, view: undefined });
86
+ const centre = surfaceCentre(part.options);
87
+ const pfx = `m${i}${tag}-`;
88
+ // A surface on an upper floor (the Kodokan's dojos) stands that high over the ground, on a floor drawn in the air; the ground under it is left bare.
89
+ const lift = part.elevation ?? 0;
90
+ // Which part of a room is drawn: what stands below the gallery's level, or what stands above it.
91
+ const lowest = (p) => Math.min(p.o[2], p.o[2] + p.u[2], p.o[2] + p.v[2], p.o[2] + p.u[2] + p.v[2]);
92
+ const shown = (p) => part.show === undefined || (part.show === "gallery") === (lowest(p) >= KODOKAN_GALLERY_LEVEL - 1e-6);
93
+ for (const item of model.uprights.items) {
94
+ const planes = item.planes.filter((p) => p.kind !== "ground" && shown(p)).map((p) => placeInDistrict(p, pp, centre, pfx, i, lift));
95
+ if (planes.length > 0) {
96
+ const [ax, ay] = item.at;
97
+ const [rx, ry] = rot([ax - centre[0], ay - centre[1]], pp.angle);
98
+ items.push({ ...item, id: `${pfx}${item.id}`, at: [rx + pp.at[0], ry + pp.at[1]], height: item.height + lift, planes });
99
+ }
90
100
  }
91
- }
92
- if (lift > 0) {
93
- const { markup, box, styles } = nestedDrawing(drawMember(member, { ...options, id: base }, i, base, { floor: true }));
94
- for (const block of styles)
95
- if (!defs.includes(block))
96
- defs.push(block);
97
- const floor = plane("floor", "floor", [box.x, box.y, 0], [box.w, 0, 0], [0, box.h, 0], `<g transform="translate(${n(-box.x)} ${n(-box.y)})">${markup}</g>`);
98
- items.push({ id: `${prefix}floor`, kind: "deck", at: placed.at, height: lift, planes: [placeInDistrict(floor, placed, centre, prefix, i, lift)] });
99
- }
101
+ if (lift > 0 && part.show !== "gallery") {
102
+ const { markup, box, styles } = nestedDrawing(drawMember(member, { ...options, id: base }, i, base, { floor: true }, j));
103
+ for (const block of styles)
104
+ if (!defs.includes(block))
105
+ defs.push(block);
106
+ const floor = plane("floor", "floor", [box.x, box.y, 0], [box.w, 0, 0], [0, box.h, 0], `<g transform="translate(${n(-box.x)} ${n(-box.y)})">${markup}</g>`);
107
+ items.push({ id: `${pfx}floor`, kind: "deck", at: pp.at, height: lift, planes: [placeInDistrict(floor, pp, centre, pfx, i, lift)] });
108
+ // The room's floor is a slab too, with an edge all round (the building's own floors do that for a chosen floor): its mats' rectangle and a hand's width more.
109
+ const spec = part.options.dojo;
110
+ if (member.plate === undefined && spec !== undefined && member.slabThickness !== undefined) {
111
+ const room = dojo(spec);
112
+ const m = 0.12 * FEET_PER_METRE;
113
+ const [w, l] = [room.width * FEET_PER_METRE, room.length * FEET_PER_METRE];
114
+ const corners = [[-m, -m], [w + m, -m], [w + m, l + m], [-m, l + m]].map(([x, y]) => {
115
+ const [rx, ry] = rot([x - centre[0], y - centre[1]], pp.angle);
116
+ return [rx + pp.at[0], ry + pp.at[1]];
117
+ });
118
+ const pixels = 1000 / Math.max(plan.view.w, plan.view.h);
119
+ const thick = Math.min(Math.max(member.slabThickness * FEET_PER_METRE, 10 / pixels), ((member.shell?.height ?? 60) / (member.shell?.storeys ?? 8)) * 0.5);
120
+ items.push({ id: `${pfx}slab`, kind: "deck", at: pp.at, height: lift, planes: slabPlanes(corners, lift, thick, { top: `${pfx}slab-top`, side: `${pfx}slab-s` }, "plain").map((p) => ({ ...p, group: i })) });
121
+ }
122
+ }
123
+ if (model.defs !== "")
124
+ defs.push(model.defs);
125
+ if (crowdStyle === "" && model.crowdStyle !== "")
126
+ crowdStyle = model.crowdStyle;
127
+ });
100
128
  // The building it stands in: see-through (its walls give way to the eye) or solid, in the venue's group so that it is stacked with it.
101
129
  if (member.shell !== undefined && options.building !== "none") {
102
- const block = shellBlock(member.key, placed.footprint, member.shell.height, options.building === "solid" ? undefined : placed.at);
130
+ // What is seen through its walls: the middle of the rooms in it (the building's own middle where a floor has none).
131
+ const inside = placed.parts.length === 0 ? placed.at : [placed.parts.reduce((s, q) => s + q.at[0], 0) / placed.parts.length, placed.parts.reduce((s, q) => s + q.at[1], 0) / placed.parts.length];
132
+ const block = shellBlock(member.key, placed.footprint, member.shell.height, options.building === "solid" ? undefined : inside);
103
133
  const planes = blockPlanes(block, `${prefix}shell-`).map((p) => ({ ...p, group: i }));
104
134
  items.push({ id: `${prefix}shell`, kind: "blocks", at: placed.at, height: member.shell.height, planes });
105
135
  }
106
- if (model.defs !== "")
107
- defs.push(model.defs);
108
- if (crowdStyle === "" && model.crowdStyle !== "")
109
- crowdStyle = model.crowdStyle;
136
+ // The floors of the building as slabs with an edge: the one chosen is picked out (its plate, with its name over it), the others are faint. A slab is as thick as the venue says and never thinner than ten pixels at the page's own size.
137
+ if (member.slabs !== undefined && options.building !== "none") {
138
+ const pixels = 1000 / Math.max(plan.view.w, plan.view.h);
139
+ const thick = Math.min(Math.max((member.slabThickness ?? 0.6) * FEET_PER_METRE, 10 / pixels), ((member.shell?.height ?? 60) / (member.shell?.storeys ?? 8)) * 0.5);
140
+ for (const slab of member.slabs) {
141
+ const chosen = member.plate?.floor === slab.floor;
142
+ // The great dojo's floor, seen with every floor, has its own slab; and a solid building hides what is in it.
143
+ if (!chosen && ((member.plate === undefined && slab.floor === 7) || options.building === "solid"))
144
+ continue;
145
+ const ids = chosen ? { top: `${prefix}plate`, side: `${prefix}plate-s` } : { top: `${prefix}plate-f${slab.floor}-top`, side: `${prefix}plate-f${slab.floor}-s` };
146
+ const planes = slabPlanes(placed.footprint, slab.level, thick, ids, chosen ? "chosen" : "faint").map((p) => ({ ...p, group: i }));
147
+ items.push({ id: ids.top, kind: "deck", at: placed.at, height: slab.level, planes });
148
+ }
149
+ }
150
+ if (member.plate !== undefined) {
151
+ const { floor, level } = member.plate;
152
+ const label = KODOKAN_FLOORS.find((f) => f.floor === floor)?.label[language] ?? String(floor);
153
+ const size = Math.max(plan.view.w, plan.view.h) * 0.018;
154
+ items.push({ id: `${prefix}plate-name`, kind: "scoreboard", at: placed.at, height: level + size * 1.7, planes: [sign(`${prefix}plate-name`, label, placed.at, level + size * 0.4, size, i)] });
155
+ }
110
156
  });
111
157
  // The buildings round the venues stand apart from them: each block's planes are of no venue's group.
112
158
  if (plan.context !== undefined)
@@ -128,7 +174,7 @@ export function buildDistrictTurn(options, settings = {}) {
128
174
  const planes = planesOf(uprights);
129
175
  const box = sceneBox(planes, area, 0, centre, scale);
130
176
  // (The floor has no names: the 3D view's own signs, which face the eye, carry them.)
131
- const floor = drawDistrict({ ...options, id: base, labels: false }, plan, (member, i) => drawMember(member, { ...options, id: base }, i, base, { floor: true }));
177
+ const floor = drawDistrict({ ...options, id: base, labels: false }, plan, (member, i, part) => drawMember(member, { ...options, id: base }, i, base, { floor: true }, part));
132
178
  const { title, description } = describeDistrict(options, plan);
133
179
  const presets = districtPresets(plan);
134
180
  const surfaceLike = { sport: "soccer", language, ...(options.theme === undefined ? {} : { theme: options.theme }), ...(options.style === undefined ? {} : { style: options.style }) };
package/dist/kodokan.d.ts CHANGED
@@ -23,6 +23,94 @@ export declare const KODOKAN: {
23
23
  readonly side: 31.8;
24
24
  readonly clearance: 0.18;
25
25
  };
26
+ readonly floors: {
27
+ readonly "5": {
28
+ readonly heading: 167;
29
+ readonly clearance: 2.59;
30
+ readonly rooms: readonly [{
31
+ readonly id: "womens";
32
+ readonly jo: 240;
33
+ readonly w: 27.3;
34
+ readonly l: 14.56;
35
+ readonly centre: readonly [139.753353, 35.707641];
36
+ }, {
37
+ readonly id: "boys";
38
+ readonly jo: 114;
39
+ readonly w: 17.29;
40
+ readonly l: 10.92;
41
+ readonly centre: readonly [139.75352, 35.707624];
42
+ }, {
43
+ readonly id: "special";
44
+ readonly jo: 40;
45
+ readonly w: 9.1;
46
+ readonly l: 7.28;
47
+ readonly centre: readonly [139.753483, 35.707752];
48
+ }];
49
+ };
50
+ readonly "6": {
51
+ readonly heading: 77;
52
+ readonly clearance: 2.43;
53
+ readonly rooms: readonly [{
54
+ readonly id: "school";
55
+ readonly jo: 240;
56
+ readonly w: 27.3;
57
+ readonly l: 14.56;
58
+ readonly centre: readonly [139.7534, 35.707724];
59
+ }, {
60
+ readonly id: "international";
61
+ readonly jo: 192;
62
+ readonly w: 21.84;
63
+ readonly l: 14.56;
64
+ readonly centre: readonly [139.75344, 35.707583];
65
+ }];
66
+ };
67
+ };
26
68
  };
27
- /** The Kodokan as a member of a district. */
28
- export declare function kodokanMember(language: Language): DistrictMember;
69
+ /** A room of a floor of the Kodokan: what the Kodokan's floor guide calls it, its count of jō where the guide gives one, and whether the room is drawn from that count or is ours. */
70
+ export interface KodokanRoom {
71
+ id: string;
72
+ name: {
73
+ en: string;
74
+ ja: string;
75
+ };
76
+ /** Mats, in jō: the guide's count; `null` where it gives none (the special dojo: drawn at 40 jō, ours). */
77
+ mats: number | null;
78
+ /** The count the room is drawn at: the guide's, or ours where it gives none. */
79
+ drawn: number;
80
+ }
81
+ /** A floor of the Kodokan International Judo Center, the new building: its label, what the Kodokan's own floor guide lists on it, what is drawn there, and where the words come from. */
82
+ export interface KodokanFloor {
83
+ /** 0 is the basement, 1 to 8 the floors above the ground. */
84
+ floor: number;
85
+ label: {
86
+ en: string;
87
+ ja: string;
88
+ };
89
+ /** What the floor guide lists on it, in each language (a floor with no entry of its own says what a second source says, `else`). */
90
+ lists: {
91
+ en: string;
92
+ ja: string;
93
+ };
94
+ rooms: KodokanRoom[];
95
+ /** What the view draws: the `rooms` the guide counts, the `great-dojo`, its `gallery`, only the floor's `plate` (nothing is published of its rooms), or `none` (under the ground). */
96
+ drawn: "rooms" | "great-dojo" | "gallery" | "plate" | "none";
97
+ /** Where the floor's words come from: the Kodokan's floor guide, and English Wikipedia where the guide says nothing. */
98
+ source: "kodokan" | "wikipedia";
99
+ }
100
+ /** The floors, from the basement up. The Kodokan's floor guide (施設のご案内, `docs/district.md`) lists 8F to B1; its 1F entry is empty, so the 1st floor is English Wikipedia's. */
101
+ export declare const KODOKAN_FLOORS: readonly KodokanFloor[];
102
+ /** The floors a district can be asked for: 0 (the basement) to 8. */
103
+ export declare const KODOKAN_FLOOR_IDS: readonly number[];
104
+ /** The height of a floor's level above the ground, in metres: the building's height over its storeys, for each floor under it (ours: the storeys are not all one height). */
105
+ export declare const kodokanLevel: (floor: number) => number;
106
+ /** How thick a floor of the building is drawn, in metres: its slab and its beams. Ours: a reinforced-concrete floor with its beams is some 0.4 to 0.8 metres of structure; the figure is a plausible one, and a floor is drawn no thinner than 10 pixels at the page's own size (`kodokanSlab`). */
107
+ export declare const KODOKAN_SLAB = 0.6;
108
+ /** The level of a floor's slab, in metres: the building's own storeys, except that the 7th is the great dojo's floor and the 8th the gallery's level over it. */
109
+ export declare const kodokanSlabLevel: (floor: number) => number;
110
+ /** The height of the gallery's level over the main dojo's floor, in feet: the room's walls, 4.5 metres. A plane that begins above it is the 8th floor's. */
111
+ export declare const KODOKAN_GALLERY_LEVEL: number;
112
+ /**
113
+ * The Kodokan as a member of a district. With a `floor`, it is that floor of the building: the 7th is the great dojo, the 8th its gallery, the 5th and 6th their dojos as the floor guide counts them (laid out by us), the others plain floor plates;
114
+ * the basement is under the ground and draws nothing. Throws for a floor that is not 0 to 8.
115
+ */
116
+ export declare function kodokanMember(language: Language, floor?: number): DistrictMember;
@@ -15,4 +15,47 @@ export declare const KODOKAN_BUILDING: {
15
15
  readonly side: 31.8;
16
16
  readonly clearance: 0.18;
17
17
  };
18
+ /** The rooms of the 5th and 6th floors: each floor's heading (of its rooms' x axis), and for each room its count in jo, its size in metres (the dojo builder's shape for the count) and the middle of it, `[longitude, latitude]`. Ours but the counts. */
19
+ readonly floors: {
20
+ readonly "5": {
21
+ readonly heading: 167;
22
+ readonly clearance: 2.59;
23
+ readonly rooms: readonly [{
24
+ readonly id: "womens";
25
+ readonly jo: 240;
26
+ readonly w: 27.3;
27
+ readonly l: 14.56;
28
+ readonly centre: readonly [139.753353, 35.707641];
29
+ }, {
30
+ readonly id: "boys";
31
+ readonly jo: 114;
32
+ readonly w: 17.29;
33
+ readonly l: 10.92;
34
+ readonly centre: readonly [139.75352, 35.707624];
35
+ }, {
36
+ readonly id: "special";
37
+ readonly jo: 40;
38
+ readonly w: 9.1;
39
+ readonly l: 7.28;
40
+ readonly centre: readonly [139.753483, 35.707752];
41
+ }];
42
+ };
43
+ readonly "6": {
44
+ readonly heading: 77;
45
+ readonly clearance: 2.43;
46
+ readonly rooms: readonly [{
47
+ readonly id: "school";
48
+ readonly jo: 240;
49
+ readonly w: 27.3;
50
+ readonly l: 14.56;
51
+ readonly centre: readonly [139.7534, 35.707724];
52
+ }, {
53
+ readonly id: "international";
54
+ readonly jo: 192;
55
+ readonly w: 21.84;
56
+ readonly l: 14.56;
57
+ readonly centre: readonly [139.75344, 35.707583];
58
+ }];
59
+ };
60
+ };
18
61
  };
@@ -15,4 +15,6 @@ export const KODOKAN_BUILDING = {
15
15
  "storeys": 8,
16
16
  /** The middle of the main dojo's room, `[longitude, latitude]`, the compass heading of its x axis (0 to under 180) and the length of its side in metres, walls and gallery included. */
17
17
  "room": { "centre": [139.75342, 35.707654], "heading": 77, "side": 31.8, "clearance": 0.18 },
18
+ /** The rooms of the 5th and 6th floors: each floor's heading (of its rooms' x axis), and for each room its count in jo, its size in metres (the dojo builder's shape for the count) and the middle of it, `[longitude, latitude]`. Ours but the counts. */
19
+ "floors": { "5": { "heading": 167, "clearance": 2.59, "rooms": [{ "id": "womens", "jo": 240, "w": 27.3, "l": 14.56, "centre": [139.753353, 35.707641] }, { "id": "boys", "jo": 114, "w": 17.29, "l": 10.92, "centre": [139.75352, 35.707624] }, { "id": "special", "jo": 40, "w": 9.1, "l": 7.28, "centre": [139.753483, 35.707752] }] }, "6": { "heading": 77, "clearance": 2.43, "rooms": [{ "id": "school", "jo": 240, "w": 27.3, "l": 14.56, "centre": [139.7534, 35.707724] }, { "id": "international", "jo": 192, "w": 21.84, "l": 14.56, "centre": [139.75344, 35.707583] }] } },
18
20
  };
package/dist/kodokan.js CHANGED
@@ -14,9 +14,56 @@ export const KODOKAN_ROOM_HEIGHT = 11.2;
14
14
  export const KODOKAN_FLOOR_HEIGHT = Math.min((6 * BUILDING.height) / BUILDING.storeys, BUILDING.height - KODOKAN_ROOM_HEIGHT);
15
15
  /** Where the Kodokan's building stands and what it is: for tests and pages that say it. */
16
16
  export const KODOKAN = { ...BUILDING, floorHeight: KODOKAN_FLOOR_HEIGHT, roomHeight: KODOKAN_ROOM_HEIGHT, mats: 420, seats: 900 };
17
- /** The Kodokan as a member of a district. */
18
- export function kodokanMember(language) {
19
- return {
17
+ /** The floors, from the basement up. The Kodokan's floor guide (施設のご案内, `docs/district.md`) lists 8F to B1; its 1F entry is empty, so the 1st floor is English Wikipedia's. */
18
+ export const KODOKAN_FLOORS = [
19
+ { floor: 0, label: { en: "B1", ja: "地下1階" }, lists: { en: "dining hall, training room, disaster-prevention centre", ja: "食堂、トレーニングルーム、防災センター" }, rooms: [], drawn: "none", source: "kodokan" },
20
+ { floor: 1, label: { en: "1F", ja: "1階" }, lists: { en: "parking, a bank and a store (the floor guide lists nothing here)", ja: "駐車場、銀行、売店(いずれも公式の案内には記載なし)" }, rooms: [], drawn: "plate", source: "wikipedia" },
21
+ { floor: 2, label: { en: "2F", ja: "2階" }, lists: { en: "museum and library, classroom, meeting rooms 3 and 4", ja: "資料館・図書館、教室、第3会議室、第4会議室" }, rooms: [], drawn: "plate", source: "kodokan" },
22
+ { floor: 3, label: { en: "3F", ja: "3階" }, lists: { en: "accommodation", ja: "宿泊施設" }, rooms: [], drawn: "plate", source: "kodokan" },
23
+ { floor: 4, label: { en: "4F", ja: "4階" }, lists: { en: "dojo guidance office, dojo reception, changing rooms", ja: "道場指導部、道場受付、更衣室(ロッカールーム)" }, rooms: [], drawn: "plate", source: "kodokan" },
24
+ {
25
+ floor: 5,
26
+ label: { en: "5F", ja: "5階" },
27
+ lists: { en: "women's dojo (240 jō), boys' dojo (114 jō), special dojo (no count given)", ja: "女子部道場(240畳)、少年部道場(114畳)、特別道場(畳数の記載なし)" },
28
+ rooms: [
29
+ { id: "womens", name: { en: "Women's dojo", ja: "女子部道場" }, mats: 240, drawn: 240 },
30
+ { id: "boys", name: { en: "Boys' dojo", ja: "少年部道場" }, mats: 114, drawn: 114 },
31
+ { id: "special", name: { en: "Special dojo", ja: "特別道場" }, mats: null, drawn: 40 },
32
+ ],
33
+ drawn: "rooms",
34
+ source: "kodokan",
35
+ },
36
+ {
37
+ floor: 6,
38
+ label: { en: "6F", ja: "6階" },
39
+ lists: { en: "school dojo (240 jō), international dojo (192 jō)", ja: "学校道場(240畳)、国際部道場(192畳)" },
40
+ rooms: [
41
+ { id: "school", name: { en: "School dojo", ja: "学校道場" }, mats: 240, drawn: 240 },
42
+ { id: "international", name: { en: "International dojo", ja: "国際部道場" }, mats: 192, drawn: 192 },
43
+ ],
44
+ drawn: "rooms",
45
+ source: "kodokan",
46
+ },
47
+ { floor: 7, label: { en: "7F", ja: "7階" }, lists: { en: "main dojo (420 jō)", ja: "大道場(420畳)" }, rooms: [{ id: "main", name: { en: "Main dojo", ja: "大道場" }, mats: 420, drawn: 420 }], drawn: "great-dojo", source: "kodokan" },
48
+ { floor: 8, label: { en: "8F", ja: "8階" }, lists: { en: "spectators' gallery (about 900 seats)", ja: "見学席(約900席)" }, rooms: [], drawn: "gallery", source: "kodokan" },
49
+ ];
50
+ /** The floors a district can be asked for: 0 (the basement) to 8. */
51
+ export const KODOKAN_FLOOR_IDS = KODOKAN_FLOORS.map((f) => f.floor);
52
+ /** The height of a floor's level above the ground, in metres: the building's height over its storeys, for each floor under it (ours: the storeys are not all one height). */
53
+ export const kodokanLevel = (floor) => ((floor - 1) * BUILDING.height) / BUILDING.storeys;
54
+ /** How thick a floor of the building is drawn, in metres: its slab and its beams. Ours: a reinforced-concrete floor with its beams is some 0.4 to 0.8 metres of structure; the figure is a plausible one, and a floor is drawn no thinner than 10 pixels at the page's own size (`kodokanSlab`). */
55
+ export const KODOKAN_SLAB = 0.6;
56
+ /** The level of a floor's slab, in metres: the building's own storeys, except that the 7th is the great dojo's floor and the 8th the gallery's level over it. */
57
+ export const kodokanSlabLevel = (floor) => (floor === 7 ? KODOKAN_FLOOR_HEIGHT : floor === 8 ? KODOKAN_FLOOR_HEIGHT + KODOKAN_GALLERY_LEVEL / FEET_PER_METRE : kodokanLevel(floor));
58
+ /** The height of the gallery's level over the main dojo's floor, in feet: the room's walls, 4.5 metres. A plane that begins above it is the 8th floor's. */
59
+ export const KODOKAN_GALLERY_LEVEL = 4.4 * FEET_PER_METRE;
60
+ const roomOptions = (jo, language) => ({ sport: "dojo", dojo: { mats: jo, surround: 0, kamiza: "north" }, crowd: false, language });
61
+ /**
62
+ * The Kodokan as a member of a district. With a `floor`, it is that floor of the building: the 7th is the great dojo, the 8th its gallery, the 5th and 6th their dojos as the floor guide counts them (laid out by us), the others plain floor plates;
63
+ * the basement is under the ground and draws nothing. Throws for a floor that is not 0 to 8.
64
+ */
65
+ export function kodokanMember(language, floor) {
66
+ const base = {
20
67
  key: "kodokan",
21
68
  id: "kodokan",
22
69
  kind: "hall",
@@ -30,5 +77,16 @@ export function kodokanMember(language) {
30
77
  elevation: KODOKAN_FLOOR_HEIGHT * FEET_PER_METRE,
31
78
  shell: { height: BUILDING.height * FEET_PER_METRE, storeys: BUILDING.storeys },
32
79
  attribution: PLATEAU_ATTRIBUTION,
80
+ slabs: KODOKAN_FLOORS.filter((f) => f.floor >= 1).map((f) => ({ floor: f.floor, level: kodokanSlabLevel(f.floor) * FEET_PER_METRE })),
81
+ slabThickness: KODOKAN_SLAB,
33
82
  };
83
+ if (floor === undefined)
84
+ return base;
85
+ if (!Number.isInteger(floor) || !KODOKAN_FLOOR_IDS.includes(floor))
86
+ throw new Error(`the Kodokan has floors ${KODOKAN_FLOOR_IDS.join(", ")} (0 is the basement), not ${String(floor)}`);
87
+ const main = (show) => ({ key: "main", options: base.options, centre: base.centre, heading: base.heading, elevation: base.elevation, show });
88
+ const rooms = BUILDING.floors[String(floor)];
89
+ const parts = floor === 7 ? [main("room")] : floor === 8 ? [main("gallery")] : rooms === undefined ? [] : rooms.rooms.map((r) => ({ key: r.id, options: roomOptions(r.jo, language), centre: r.centre, heading: rooms.heading, elevation: kodokanLevel(floor) * FEET_PER_METRE }));
90
+ // The great dojo's own floor is the 7th's (a little under its storey height, so that the room stays under the roof), and the gallery's level is the room's wall height over it.
91
+ return { ...base, parts, ...(floor >= 1 ? { plate: { floor, level: kodokanSlabLevel(floor) * FEET_PER_METRE } } : {}) };
34
92
  }
@@ -78,6 +78,11 @@ export declare function nudge(view: TurnView, toward: TurnToward, amount: number
78
78
  * yaw falls) and a drag down tips the top toward the eye (the pitch falls).
79
79
  */
80
80
  export declare function drag(view: TurnView, dx: number, dy: number, tilt?: boolean, degreesPerPixel?: number): TurnView;
81
+ /**
82
+ * The view after the wheel turns by `deltaY` (positive is down, as `WheelEvent.deltaY` says) while the view has focus: the wheel up tips the surface as a drag up does (the near edge goes up, the
83
+ * front is lifted: the pitch rises) and the wheel down as a drag down does.
84
+ */
85
+ export declare const wheelTip: (view: TurnView, deltaY: number) => TurnView;
81
86
  /** A box in the scene, in pixels from the scene's middle, the floor at `z0` and the highest thing at `z1`. */
82
87
  export interface Box3 {
83
88
  x0: number;
@@ -100,6 +100,11 @@ export function nudge(view, toward, amount) {
100
100
  export function drag(view, dx, dy, tilt = true, degreesPerPixel = 0.4) {
101
101
  return clampView({ ...view, yaw: view.yaw - dx * degreesPerPixel, pitch: tilt ? view.pitch - dy * degreesPerPixel * 0.75 : view.pitch });
102
102
  }
103
+ /**
104
+ * The view after the wheel turns by `deltaY` (positive is down, as `WheelEvent.deltaY` says) while the view has focus: the wheel up tips the surface as a drag up does (the near edge goes up, the
105
+ * front is lifted: the pitch rises) and the wheel down as a drag down does.
106
+ */
107
+ export const wheelTip = (view, deltaY) => clampView({ ...view, pitch: view.pitch - deltaY * 0.15 });
103
108
  const RAD = Math.PI / 180;
104
109
  /** Where a point of the scene falls on the screen, in pixels from the middle of the container, when the scene is placed as given and the eye is `distance` pixels away. */
105
110
  export function project(point, view, at, distance) {
package/dist/turn.dom.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // reduced motion gets jumps for glides, and where the browser cannot draw in 3D the flat drawing stays. Nothing is polled and nothing runs while the view is
4
4
  // still: the elements' `transform`s are written when the view changes, and a glide between viewpoints is a short run of animation frames.
5
5
  import { drawKyogijo } from "./draw.js";
6
- import { STEP, TURN_PRESET_NAMES, clampView, drag, nearest, nudge, presetOf, presetView, step, turnDirection } from "./turn.camera.js";
6
+ import { STEP, TURN_PRESET_NAMES, clampView, drag, nearest, nudge, presetOf, presetView, step, turnDirection, wheelTip } from "./turn.camera.js";
7
7
  import { buildTurn, floorOptions, openingView, turnWords } from "./turn.model.js";
8
8
  import { controlsMarkup, layoutOf, turnMarkupOf, worldMarkup } from "./turn.markup.js";
9
9
  import { KYOGIJO_TURN_STYLE } from "./turn.style.js";
@@ -438,7 +438,7 @@ export function mountTurn(host, options, settings, build) {
438
438
  else if (doc.activeElement === stage) {
439
439
  event.preventDefault();
440
440
  grab();
441
- view = event.shiftKey || Math.abs(event.deltaX) > Math.abs(event.deltaY) ? clampView({ ...view, yaw: view.yaw + (event.deltaX || event.deltaY) * 0.25 }) : clampView({ ...view, pitch: view.pitch + event.deltaY * 0.15 });
441
+ view = event.shiftKey || Math.abs(event.deltaX) > Math.abs(event.deltaY) ? clampView({ ...view, yaw: view.yaw + (event.deltaX || event.deltaY) * 0.25 }) : wheelTip(view, event.deltaY);
442
442
  }
443
443
  else
444
444
  return;
@@ -40,6 +40,8 @@ export interface Plane {
40
40
  * (a raised room is seen through every wall between it and the eye, at a low angle and from straight above alike).
41
41
  */
42
42
  fadeSide?: boolean;
43
+ /** A plate the other planes of its building stand on (a floor of a building picked out): it is placed by its farthest corner, so that it is drawn under everything on it, whatever way the view turns. */
44
+ under?: boolean;
43
45
  /**
44
46
  * The building (a `TurnModel.groups` index) a plane belongs to, in a scene of several that stand apart (a district's): the view draws one building's planes all before another's, by which side of the line between
45
47
  * them the eye is on, and the planes of one building among themselves by depth.
@@ -66,7 +66,14 @@ export function layoutOf(model, view, width, height, lens) {
66
66
  const transforms = [floor, ...planes].map((p) => matrix3d(multiply(camera, planeMatrix(p, s, centre))));
67
67
  // Far to near: a plane's depth is that of its middle. The ground of a hall is under everything. The floor is next, unless it stands raised on something (a table tennis table's top, on its legs): then it is
68
68
  // sorted with the rest, by its middle, so that the legs and the skirt on the far side are drawn under it and the ones on the near side over it, and nothing under the top shows through it.
69
- const depth = (p) => depthOf([(p.o[0] + (p.u[0] + p.v[0]) / 2 - centre[0]) * s, (p.o[1] + (p.u[1] + p.v[1]) / 2 - centre[1]) * s, (p.o[2] + (p.u[2] + p.v[2]) / 2) * s], view, at);
69
+ const depth = (p) => {
70
+ // A see-through building's wall is a tall plane round what stands in it: a far wall is behind all of it and a near one in front of all of it, however high its middle is, so a far wall is placed by its foot and a near one by its top.
71
+ const zs = [p.o[2], p.o[2] + p.u[2], p.o[2] + p.v[2], p.o[2] + p.u[2] + p.v[2]];
72
+ const z = p.fadeSide === true ? (fadeOf(p, view.yaw, view.pitch) >= 1 ? Math.min(...zs) : Math.max(...zs)) : (p.o[2] + (p.u[2] + p.v[2]) / 2);
73
+ if (p.under === true)
74
+ return Math.min(...[[0, 0], [1, 0], [0, 1], [1, 1]].map(([a, b]) => depthOf([(p.o[0] + a * p.u[0] + b * p.v[0] - centre[0]) * s, (p.o[1] + a * p.u[1] + b * p.v[1] - centre[1]) * s, (p.o[2] + a * p.u[2] + b * p.v[2]) * s], view, at)));
75
+ return depthOf([(p.o[0] + (p.u[0] + p.v[0]) / 2 - centre[0]) * s, (p.o[1] + (p.u[1] + p.v[1]) / 2 - centre[1]) * s, z * s], view, at);
76
+ };
70
77
  const raised = model.uprights.lift > 0;
71
78
  const order = [...planes.map((p, i) => ({ i: i + 1, ground: p.kind === "ground", depth: depth(p), group: p.group })), ...(raised ? [{ i: 0, ground: false, depth: depth(floor), group: undefined }] : [])].filter((one) => !one.ground);
72
79
  // Buildings that stand apart are drawn one after another, the far one first; a plane of none (the floor) goes by its depth alone.
@@ -5,9 +5,9 @@ import { KYOGIJO_TURN_BOARD_STYLE } from "./overlay.js";
5
5
  * 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.
6
6
  */
7
7
  export const KYOGIJO_TURN_STYLE = `
8
- :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-ivy:#5b8a45;--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-cagepad:#2a2d32;--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};--ky-up-blk-a:#e9ece4;--ky-up-blk-b:#c9cdc3;--ky-up-blk-c:#a4aa9f;--ky-up-blk-roof:#f4f5f0;--ky-up-blk-line:rgba(40,46,42,.38)}
9
- @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-ivy:#3d6633;--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-cagepad:#8f969f;--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};--ky-up-blk-a:#59626b;--ky-up-blk-b:#444b53;--ky-up-blk-c:#30363c;--ky-up-blk-roof:#69727b;--ky-up-blk-line:rgba(220,226,220,.3)}}
10
- :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-ivy:#3d6633;--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-cagepad:#8f969f;--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};--ky-up-blk-a:#59626b;--ky-up-blk-b:#444b53;--ky-up-blk-c:#30363c;--ky-up-blk-roof:#69727b;--ky-up-blk-line:rgba(220,226,220,.3)}
8
+ :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-ivy:#5b8a45;--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-cagepad:#2a2d32;--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};--ky-up-blk-a:#e9ece4;--ky-up-blk-b:#c9cdc3;--ky-up-blk-c:#a4aa9f;--ky-up-blk-roof:#f4f5f0;--ky-up-blk-line:rgba(40,46,42,.38);--ky-up-plate:rgba(47,109,176,.5);--ky-up-plate-line:#2f6db0}
9
+ @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-ivy:#3d6633;--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-cagepad:#8f969f;--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};--ky-up-blk-a:#59626b;--ky-up-blk-b:#444b53;--ky-up-blk-c:#30363c;--ky-up-blk-roof:#69727b;--ky-up-blk-line:rgba(220,226,220,.3);--ky-up-plate:rgba(127,176,232,.45);--ky-up-plate-line:#7fb0e8}}
10
+ :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-ivy:#3d6633;--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-cagepad:#8f969f;--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};--ky-up-blk-a:#59626b;--ky-up-blk-b:#444b53;--ky-up-blk-c:#30363c;--ky-up-blk-roof:#69727b;--ky-up-blk-line:rgba(220,226,220,.3);--ky-up-plate:rgba(127,176,232,.45);--ky-up-plate-line:#7fb0e8}
11
11
  .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)}
12
12
  .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}
13
13
  .ky-turn[data-touch=turn] .ky-turn-view{touch-action:none}
@@ -73,6 +73,12 @@ export const KYOGIJO_TURN_STYLE = `
73
73
  .ky-turn .u-block-a{fill:var(--ky-up-blk-a)}
74
74
  .ky-turn .u-block-b{fill:var(--ky-up-blk-b)}
75
75
  .ky-turn .u-block-c{fill:var(--ky-up-blk-c)}
76
+ .ky-turn .u-slab{stroke:var(--ky-up-blk-line);stroke-width:.5}
77
+ .ky-turn .u-slab-a{fill:var(--ky-up-blk-a)}
78
+ .ky-turn .u-slab-b{fill:var(--ky-up-blk-b)}
79
+ .ky-turn .u-slab-c{fill:var(--ky-up-blk-c)}
80
+ .ky-turn .u-slab-top{fill:var(--ky-up-blk-roof);stroke:var(--ky-up-blk-line);stroke-width:.5}
81
+ .ky-turn .u-floorplate{fill:var(--ky-up-plate);stroke:var(--ky-up-plate-line);stroke-width:1.2;stroke-linejoin:round}
76
82
  .ky-turn .u-block-roof{fill:var(--ky-up-blk-roof);stroke:var(--ky-up-blk-line);stroke-width:.5;stroke-linejoin:round}
77
83
  .ky-turn .u-roof{fill:var(--ky-up-roof);stroke:var(--ky-up-roofline);stroke-width:.8;stroke-linejoin:round}
78
84
  .ky-turn .u-signplate{fill:var(--ky-turn-bg);fill-opacity:.9;stroke:var(--ky-turn-rule);stroke-width:.5}
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.43.0";
2
+ export declare const VERSION = "0.44.1";
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.43.0";
2
+ export const VERSION = "0.44.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/kyogijo-stadiums",
3
- "version": "0.43.0",
3
+ "version": "0.44.1",
4
4
  "description": "Kyogijo Stadiums 競技場, 40 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, the MMA cage, 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",
@@ -148,6 +148,11 @@
148
148
  "import": "./dist/plateau-entry.js",
149
149
  "default": "./dist/plateau-entry.js"
150
150
  },
151
+ "./cityctx": {
152
+ "types": "./dist/cityctx-entry.d.ts",
153
+ "import": "./dist/cityctx-entry.js",
154
+ "default": "./dist/cityctx-entry.js"
155
+ },
151
156
  "./osm": {
152
157
  "types": "./dist/osm-entry.d.ts",
153
158
  "import": "./dist/osm-entry.js",
@@ -317,6 +322,9 @@
317
322
  "stadiums:fetch": "node scripts/fetch-stadiums.mjs",
318
323
  "walk": "node scripts/build-walk.mjs",
319
324
  "osm-context": "node scripts/build-osm-context.mjs",
325
+ "cityctx": "node scripts/build-cityctx.mjs",
326
+ "cityctx:fetch": "node scripts/fetch-cityctx.mjs",
327
+ "cityctx:vox": "python3.13 -I scripts/cityctx_vox.py",
320
328
  "walk:fetch": "node scripts/fetch-walk.mjs",
321
329
  "outside:cost": "node scripts/outside-cost.mjs",
322
330
  "plateau": "node scripts/build-plateau.mjs",