@johnmorrisdotca/kyogijo-stadiums 0.40.1 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/NOTICE.md +17 -0
- package/README.md +1 -3
- package/dist/context.d.ts +141 -0
- package/dist/context.js +290 -0
- package/dist/crowd.d.ts +33 -1
- package/dist/crowd.draw.d.ts +1 -1
- package/dist/crowd.draw.js +16 -2
- package/dist/crowd.js +9 -1
- package/dist/district-entry.d.ts +14 -0
- package/dist/district-entry.js +9 -0
- package/dist/district.d.ts +54 -1
- package/dist/district.dom.d.ts +31 -0
- package/dist/district.dom.js +52 -0
- package/dist/district.js +71 -11
- package/dist/district.turn.js +30 -6
- package/dist/draw.js +37 -13
- package/dist/gates.d.ts +56 -0
- package/dist/gates.js +95 -0
- package/dist/halls.d.ts +2 -0
- package/dist/halls.js +1 -1
- package/dist/kodokan.d.ts +28 -0
- package/dist/kodokan.data.d.ts +18 -0
- package/dist/kodokan.data.js +18 -0
- package/dist/kodokan.js +34 -0
- package/dist/osm-entry.d.ts +2 -0
- package/dist/osm-entry.js +2 -0
- package/dist/osm.context.data.d.ts +10 -0
- package/dist/osm.context.data.js +1107 -0
- package/dist/outside.d.ts +43 -0
- package/dist/outside.dom.d.ts +44 -0
- package/dist/outside.dom.js +203 -0
- package/dist/outside.js +227 -0
- package/dist/outside.paint.d.ts +57 -0
- package/dist/outside.paint.js +88 -0
- package/dist/plateau-entry.d.ts +3 -0
- package/dist/plateau-entry.js +3 -0
- package/dist/plateau.credit.d.ts +5 -0
- package/dist/plateau.credit.js +7 -0
- package/dist/plateau.data.d.ts +7 -0
- package/dist/plateau.data.js +757 -0
- package/dist/stadiums.d.ts +15 -1
- package/dist/stadiums.data.js +2 -1
- package/dist/stadiums.js +48 -12
- package/dist/strings.d.ts +3 -1
- package/dist/strings.js +4 -4
- package/dist/surfaces/surface.d.ts +5 -0
- package/dist/turn.dom.d.ts +10 -0
- package/dist/turn.dom.js +21 -0
- package/dist/turn.kit.d.ts +10 -3
- package/dist/turn.kit.js +7 -0
- package/dist/turn.markup.js +7 -2
- package/dist/turn.model.d.ts +3 -0
- package/dist/turn.style.js +15 -3
- package/dist/uprights.js +9 -1
- package/dist/venue.js +9 -3
- package/dist/venue.real.d.ts +22 -1
- package/dist/venue.real.js +76 -7
- package/dist/venues.d.ts +9 -1
- package/dist/venues.data.js +2 -1
- package/dist/venues.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/walk.d.ts +56 -0
- package/dist/walk.data.d.ts +4 -0
- package/dist/walk.data.js +1295 -0
- package/dist/walk.js +120 -0
- package/package.json +17 -1
package/dist/stadiums.d.ts
CHANGED
|
@@ -80,6 +80,13 @@ export interface RealStadiumData {
|
|
|
80
80
|
};
|
|
81
81
|
read: string;
|
|
82
82
|
};
|
|
83
|
+
/** The building's lower band, a podium under the concourse levels: its height in feet and how it is known (ours where the sources do not say). */
|
|
84
|
+
facade?: {
|
|
85
|
+
podium: number;
|
|
86
|
+
unit: "ft";
|
|
87
|
+
basis: StadiumBasis;
|
|
88
|
+
read: string;
|
|
89
|
+
};
|
|
83
90
|
roof: {
|
|
84
91
|
kind: StadiumRoof;
|
|
85
92
|
basis: StadiumBasis;
|
|
@@ -90,6 +97,13 @@ export interface RealStadiumData {
|
|
|
90
97
|
width: number;
|
|
91
98
|
unit: "m";
|
|
92
99
|
};
|
|
100
|
+
/** A cable roof's masts round its rim, as the engineering article gives them: how many, and how high they rise over the ring beam, in metres (the source is named, apart from Wikipedia's, in `source`). */
|
|
101
|
+
masts?: {
|
|
102
|
+
count: number;
|
|
103
|
+
height: number;
|
|
104
|
+
unit: "m";
|
|
105
|
+
source: string;
|
|
106
|
+
};
|
|
93
107
|
/** A canopy's share of the seats it covers, as the article gives it. */
|
|
94
108
|
cover?: number;
|
|
95
109
|
/** A retractable roof's bottom and top above the field, in feet, as the article gives them (an air-supported roof's height is the building's, from OpenStreetMap). */
|
|
@@ -183,7 +197,7 @@ export declare const stadiumConfigOf: (sport: string | undefined) => StadiumConf
|
|
|
183
197
|
export declare const stadiumSeats: (stadium: RealStadium, config: StadiumConfigId) => number;
|
|
184
198
|
/**
|
|
185
199
|
* The stylesheet of a stadium that stages more than one field (`data-stadium` on the drawing's root): the ground round the building is its own, the football field's, whichever field lies in it, so that a soccer pitch does not tint it
|
|
186
|
-
* green
|
|
200
|
+
* green (light only: the dark ground is one for every sport), and the crowd's dimming in the dark kept on a layer of its own.
|
|
187
201
|
*/
|
|
188
202
|
export declare const KYOGIJO_STADIUM_STYLE: string;
|
|
189
203
|
/** How far a hall's first row is from the box of the ring, on every side, in feet (ours: the seats at the ringside are a few feet from the ropes). */
|
package/dist/stadiums.data.js
CHANGED
|
@@ -7,7 +7,8 @@ export const STADIUM_DATA = [
|
|
|
7
7
|
"configs": { "cfl": { "sport": "cfl", "seats": 54500, "source": "wikipedia", "read": "the infobox's first capacity, 54,500, which Wikidata's capacity (P1083) also gives; the article's stand for football of the CFL's club" }, "soccer": { "sport": "soccer", "seats": 52497, "source": "wikipedia", "read": "the infobox's capacity for the 2026 world cup of soccer, 52,497, 2,003 fewer than its first" } },
|
|
8
8
|
"wikidataSeats": [54500],
|
|
9
9
|
"tiers": { "count": 2, "basis": "documented", "lowerSeats": 27695, "read": "the infobox's lower bowl of 27,695 seats, and the article's lower and upper bowls (the club that plays soccer there sold only the lower bowl)" },
|
|
10
|
-
"roof": { "kind": "cable", "basis": "documented", "read": "the article: a retractable roof supported by cables, the largest of its kind, its opening 100 by 85 metres (the size of the field), the fabric retracting into a pod in the middle of the opening above the suspended video board", "opening": { "length": 100, "width": 85, "unit": "m" } },
|
|
10
|
+
"roof": { "kind": "cable", "basis": "documented", "read": "the article: a retractable roof supported by cables, the largest of its kind, its opening 100 by 85 metres (the size of the field), the fabric retracting into a pod in the middle of the opening above the suspended video board", "opening": { "length": 100, "width": 85, "unit": "m" }, "masts": { "count": 36, "height": 47.5, "unit": "m", "source": "https://www.structuremag.org/article/bc-place-revitalization/ (STRUCTURE magazine, 'BC Place Revitalization': thirty-six steel perimeter masts rise 47.5 metres above the original roof's concrete ring beam, carrying a radial cable truss post-tensioned in a new steel compression ring; the roof is about 40,000 square metres, clear spans of 227 by 186 metres, its centre 7,500 square metres retractable into the centre gondola above the video scoreboard); read on 2026-10-22, facts only" } },
|
|
11
|
+
"facade": { "podium": 40, "unit": "ft", "basis": "plausible", "read": "the brief: a lower podium ring under the concourse levels, in the building's own outline; its height is ours (the sources do not say)" },
|
|
11
12
|
},
|
|
12
13
|
{
|
|
13
14
|
"id": "lumen-field",
|
package/dist/stadiums.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// field and not a rink: level one is the building, level two a plausible bowl, and it stops there (no team's, ticket seller's or federation's seat map is read or copied, no section or row is named, and no mark of a club, a
|
|
4
4
|
// league or a tournament is drawn). The drawing is `drawKyogijo({ sport: "cfl", stadium, crowd: { real: true } })` (or `sport: "soccer"` with a `ground` or a `stadium`, `sport: "mlb"` with a `park`) and, in 3D, the same options
|
|
5
5
|
// in `buildTurn`.
|
|
6
|
-
import { NORTH_ROOM, aislesOf, areaOfOutline, outlinePoints } from "./crowd.js";
|
|
6
|
+
import { NORTH_ROOM, aisleParameters, aislesOf, areaOfOutline, outlinePoints, sectorPolygon } from "./crowd.js";
|
|
7
7
|
import { parkLayout } from "./crowd.park.js";
|
|
8
8
|
import { FEET_PER_METRE, FOOTBALL } from "./geometry.js";
|
|
9
9
|
import { groundById } from "./grounds.js";
|
|
@@ -61,9 +61,12 @@ export const stadiumConfigOf = (sport) => (sport === "cfl" || sport === "nfl" ||
|
|
|
61
61
|
export const stadiumSeats = (stadium, config) => stadium.configs[config]?.seats ?? 0;
|
|
62
62
|
/**
|
|
63
63
|
* The stylesheet of a stadium that stages more than one field (`data-stadium` on the drawing's root): the ground round the building is its own, the football field's, whichever field lies in it, so that a soccer pitch does not tint it
|
|
64
|
-
* green
|
|
64
|
+
* green (light only: the dark ground is one for every sport), and the crowd's dimming in the dark kept on a layer of its own.
|
|
65
65
|
*/
|
|
66
|
-
export const KYOGIJO_STADIUM_STYLE =
|
|
66
|
+
export const KYOGIJO_STADIUM_STYLE =
|
|
67
|
+
// The crowd dims in the dark as a group (`opacity`): on a layer of its own it is drawn the same whatever is drawn over it (the field), so that two configurations of a stadium are the same crowd to the last level of a pixel.
|
|
68
|
+
".kyogijo[data-stadium] .ky-crowd{will-change:opacity}\n" +
|
|
69
|
+
"@media (prefers-color-scheme:light){:where(html:not([data-theme=dark]) .kyogijo[data-sport=soccer][data-stadium]:not([data-theme=dark])){--ky-apron:#dcd8c9}}\n" +
|
|
67
70
|
":where(html[data-theme=light] .kyogijo[data-sport=soccer][data-stadium],.kyogijo[data-theme=light][data-sport=soccer][data-stadium]){--ky-apron:#dcd8c9}";
|
|
68
71
|
// ---- The bowl. ----
|
|
69
72
|
/** How far the first row is from a football field's footprint (the league's field and its end zones) along a side and at an end, and the corner's radius: the package's own gridiron profile (`crowd.ts`), so that a real stadium's first row is the usual one. */
|
|
@@ -100,6 +103,42 @@ function solveFor(f, lo, hi, target) {
|
|
|
100
103
|
}
|
|
101
104
|
return (a + b) / 2;
|
|
102
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* The bowl with `closedSeats` of its seats closed, the ones a configuration does not open: whole bands from the last (outermost) inward, marked `closed` and drawn as empty seats, and where the seats left to close are part of a band, a block of
|
|
108
|
+
* that band at the middle of its left side (an end of the field, where the long axis ends), as many seats as comes nearest, drawn as empty seats over it. The same place every time: no random number. `area` is
|
|
109
|
+
* the floor a seat takes in the plan.
|
|
110
|
+
*/
|
|
111
|
+
function shut(rings, closedSeats, area) {
|
|
112
|
+
const out = rings.map((ring) => ({ ...ring }));
|
|
113
|
+
const sectors = [];
|
|
114
|
+
let left = closedSeats;
|
|
115
|
+
for (let k = out.length - 1; k >= 0 && left > 0; k -= 1) {
|
|
116
|
+
const ring = out[k];
|
|
117
|
+
if (left >= ring.seats - 2) {
|
|
118
|
+
ring.fill = 0;
|
|
119
|
+
ring.closed = true;
|
|
120
|
+
left -= ring.seats;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (ring.region.kind === "rrect" && ring.hole?.kind === "rrect") {
|
|
124
|
+
const [region, hole] = [ring.region, ring.hole];
|
|
125
|
+
// A block centred on the middle of the left side (6.5), widened by a tenth of a side's length (about 30 feet) at a time, as wide as comes nearest to the seats to close.
|
|
126
|
+
const step = 1 / Math.max(aisleParameters(hole).nv, 2);
|
|
127
|
+
const seatsOf = (w) => Math.round(areaOfOutline({ kind: "poly", points: sectorPolygon(region, hole, 6.5 - w / 2, 6.5 + w / 2) }) / area);
|
|
128
|
+
let width = step;
|
|
129
|
+
let made = seatsOf(width);
|
|
130
|
+
for (let i = 0; made < left && width + step < 8; i += 1) {
|
|
131
|
+
const next = seatsOf(width + step);
|
|
132
|
+
if (Math.abs(next - left) > Math.abs(made - left))
|
|
133
|
+
break;
|
|
134
|
+
[width, made] = [width + step, next];
|
|
135
|
+
}
|
|
136
|
+
sectors.push({ ring: ring.id, kind: "closed", points: sectorPolygon(region, hole, 6.5 - width / 2, 6.5 + width / 2) });
|
|
137
|
+
}
|
|
138
|
+
left = 0;
|
|
139
|
+
}
|
|
140
|
+
return { rings: out, sectors };
|
|
141
|
+
}
|
|
103
142
|
/** Plans already made, by stadium, configuration and field: a plan is a pure function of them, and a drawing asks for it more than once. */
|
|
104
143
|
const PLANS = new WeakMap();
|
|
105
144
|
/**
|
|
@@ -244,14 +283,8 @@ function plan(stadium, config, rink) {
|
|
|
244
283
|
}
|
|
245
284
|
}
|
|
246
285
|
const total = rings.reduce((sum, ring) => sum + ring.seats, 0);
|
|
247
|
-
// A configuration that opens fewer seats than the bowl holds opens them from the lowest bowl up:
|
|
248
|
-
|
|
249
|
-
let before = 0;
|
|
250
|
-
for (const ring of rings) {
|
|
251
|
-
ring.fill = round(ring.fill * Math.min(1, Math.max(0, (open - before) / Math.max(1, ring.seats))), 3);
|
|
252
|
-
before += ring.seats;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
286
|
+
// A configuration that opens fewer seats than the bowl holds opens them from the lowest bowl up: the seats it does not open are the top tier's, closed, drawn empty and in the same place every time.
|
|
287
|
+
const closing = open < seatsTotal ? shut(rings, seatsTotal - open, seatArea) : { rings, sectors: [] };
|
|
255
288
|
const top = rings[rings.length - 1].height[1];
|
|
256
289
|
const roof = stadium.osm.roof;
|
|
257
290
|
const rise = roof?.rise === undefined ? 0 : round(roof.rise * FEET_PER_METRE);
|
|
@@ -263,7 +296,8 @@ function plan(stadium, config, rink) {
|
|
|
263
296
|
const north = orientation.basis === "none" ? undefined : orientation.north;
|
|
264
297
|
const room = north === undefined ? 0 : NORTH_ROOM;
|
|
265
298
|
const bowl = {
|
|
266
|
-
rings,
|
|
299
|
+
rings: closing.rings,
|
|
300
|
+
...(closing.sectors.length === 0 ? {} : { closedSectors: closing.sectors }),
|
|
267
301
|
off: { left: rink.x - first.x0, right: first.x1 - rink.x - rink.w, top: rink.y - first.y0, bottom: first.y1 - rink.y - rink.h },
|
|
268
302
|
reach: { left: Math.max(0, -rink.w / 2 - around.x0), right: Math.max(0, around.x1 - rink.w / 2), top: Math.max(0, -rink.h / 2 - around.y0) + room, bottom: Math.max(0, around.y1 - rink.h / 2) },
|
|
269
303
|
shell: {
|
|
@@ -273,6 +307,8 @@ function plan(stadium, config, rink) {
|
|
|
273
307
|
roof: cable ? "cable" : canopies === undefined ? "fixed" : "canopy",
|
|
274
308
|
edge,
|
|
275
309
|
...(cable ? { crown: { rise, opening } } : {}),
|
|
310
|
+
...(stadium.facade === undefined ? {} : { podium: stadium.facade.podium }),
|
|
311
|
+
...(cable && stadium.roof.masts !== undefined ? { masts: { count: stadium.roof.masts.count, height: round(stadium.roof.masts.height * FEET_PER_METRE) } } : {}),
|
|
276
312
|
...(canopies === undefined ? {} : { canopies }),
|
|
277
313
|
},
|
|
278
314
|
};
|
package/dist/strings.d.ts
CHANGED
|
@@ -529,6 +529,8 @@ export interface KyogijoWords {
|
|
|
529
529
|
districtNeighbours: string;
|
|
530
530
|
districtCity: string;
|
|
531
531
|
districtCredit: string;
|
|
532
|
+
/** The people outside a district's venues: {percent} of the venues' seats, {people} people, on ground people may stand on. */
|
|
533
|
+
districtOutside: string;
|
|
532
534
|
/** A game decided on penalties: {name}, and the shoot-out score {a}-{b}, winner first. */
|
|
533
535
|
wonPens: string;
|
|
534
536
|
/** The shoot-out, as it is written beside a status: {a} and {b}, in the order of the sides on the drawing. */
|
|
@@ -1046,7 +1048,7 @@ export interface KyogijoWords {
|
|
|
1046
1048
|
item: string;
|
|
1047
1049
|
itemSag: string;
|
|
1048
1050
|
/** The names of what stands on the surface, by kind. */
|
|
1049
|
-
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" | "deck" | "cage-fence" | "cage-posts" | "ring-ropes" | "ring-posts" | "ribbon-boards" | "hung-board", string>;
|
|
1051
|
+
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" | "deck" | "cage-fence" | "cage-posts" | "ring-ropes" | "ring-posts" | "ribbon-boards" | "hung-board" | "blocks", string>;
|
|
1050
1052
|
};
|
|
1051
1053
|
/** The names of the zones that `zoneAt` answers with (rules.ts), by id. */
|
|
1052
1054
|
rules: Record<RuleId, string>;
|
package/dist/strings.js
CHANGED
|
@@ -199,7 +199,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
199
199
|
},
|
|
200
200
|
},
|
|
201
201
|
scoreTitle: "{away} {awayScore}, {home} {homeScore}",
|
|
202
|
-
said: { home: "home", away: "away", won: "{name} won.", tied: "The game is level.", park: "The outfield fence follows the figures published for {park}.", officially: "Its official name is {name}.", ground: "The pitch follows the size published for {ground}.", groundDefault: "No pitch size is published for {ground}, so the pitch has the international size of 105 by 68 metres.", arenaRink: "{arena}, hockey rink", arenaCourt: "{arena}, basketball court", arenaRinkAbout: "an ice hockey rink, {length} by {width} feet ({lengthM} by {widthM} metres) with rounded corners, with its red and blue lines, face-off circles, creases and goal nets", arenaCourtAbout: "a basketball court, {length} by {width} feet ({lengthM} by {widthM} metres), with its keys, three-point lines, free-throw circles and the centre circle", arenaSize: "The size is the one published for {arena}.", arenaUnknown: "The article says the rink of {arena} was shorter than the NHL's but gives no size, so the NHL's is drawn.", real: "The stands are a plausible bowl of {seats} seats in {tiers} tiers, fitted inside the outline of the building of {arena} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the arena's plan.", realBuilding: "The rink lies along the building's long axis, which is an inference, and the arrow points north.", realMapped: "The rink lies as OpenStreetMap maps the ice, and the arrow points north.", realNone: "The rink's orientation is not known, so it is drawn east-west with north at the top.", stadium: "The stands are a plausible bowl of {seats} seats in {tiers} bowls, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the stadium's plan.", stadiumOpening: "The field lies along the opening in the roof, which is the size of the field, and the arrow points north.", stadiumBuilding: "The field lies along the building's long axis, which is an inference, and the arrow points north.", stadiumNone: "The field's orientation is not known, so it is drawn east-west with north at the top.", stadiumHall: "The stands are a plausible bowl of {seats} seats in {tiers} levels, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the hall's plan.", stadiumRingBuilding: "The ring is set along the building's long axis, which is an inference, and the arrow points north.", stadiumPitchPark: "The way home plate faces is worked out from the grass of the field OpenStreetMap maps, fitted to the published fence, and the arrow points north.", stadiumNonePark: "The way home plate faces is not known, so the field is drawn the way that fits the building, and no arrow points north.", stadiumPark: "The stands are the usual stands of a ballpark, each band as deep as its seats need, for {seats} seats, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the park's plan.", stadiumPitch: "The field lies as OpenStreetMap maps the pitch, and the arrow points north.", stadiumInfield: "The way home plate faces is worked out from the infield OpenStreetMap maps and the building's outline, and the arrow points north.", district: "A plan of {count} real venues at their real positions and orientations, from the outlines OpenStreetMap maps; {mode}.", districtNeighbours: "it is fitted close round the buildings", districtCity: "it keeps the real distances, with a grid of kilometres and a scale", districtCredit: "Credit:", era: "The markings are those of the {season} season.", hammer: "{name} has the hammer.", ends: "By end, {name}: {list}.", total: "Total", wonPens: "{name} won {a}-{b} on penalties.", pens: "{a}-{b} pens", on: "Drawn as", sep: ", ", stop: ".", colon: ": ", gap: " " },
|
|
202
|
+
said: { home: "home", away: "away", won: "{name} won.", tied: "The game is level.", park: "The outfield fence follows the figures published for {park}.", officially: "Its official name is {name}.", ground: "The pitch follows the size published for {ground}.", groundDefault: "No pitch size is published for {ground}, so the pitch has the international size of 105 by 68 metres.", arenaRink: "{arena}, hockey rink", arenaCourt: "{arena}, basketball court", arenaRinkAbout: "an ice hockey rink, {length} by {width} feet ({lengthM} by {widthM} metres) with rounded corners, with its red and blue lines, face-off circles, creases and goal nets", arenaCourtAbout: "a basketball court, {length} by {width} feet ({lengthM} by {widthM} metres), with its keys, three-point lines, free-throw circles and the centre circle", arenaSize: "The size is the one published for {arena}.", arenaUnknown: "The article says the rink of {arena} was shorter than the NHL's but gives no size, so the NHL's is drawn.", real: "The stands are a plausible bowl of {seats} seats in {tiers} tiers, fitted inside the outline of the building of {arena} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the arena's plan.", realBuilding: "The rink lies along the building's long axis, which is an inference, and the arrow points north.", realMapped: "The rink lies as OpenStreetMap maps the ice, and the arrow points north.", realNone: "The rink's orientation is not known, so it is drawn east-west with north at the top.", stadium: "The stands are a plausible bowl of {seats} seats in {tiers} bowls, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the stadium's plan.", stadiumOpening: "The field lies along the opening in the roof, which is the size of the field, and the arrow points north.", stadiumBuilding: "The field lies along the building's long axis, which is an inference, and the arrow points north.", stadiumNone: "The field's orientation is not known, so it is drawn east-west with north at the top.", stadiumHall: "The stands are a plausible bowl of {seats} seats in {tiers} levels, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the hall's plan.", stadiumRingBuilding: "The ring is set along the building's long axis, which is an inference, and the arrow points north.", stadiumPitchPark: "The way home plate faces is worked out from the grass of the field OpenStreetMap maps, fitted to the published fence, and the arrow points north.", stadiumNonePark: "The way home plate faces is not known, so the field is drawn the way that fits the building, and no arrow points north.", stadiumPark: "The stands are the usual stands of a ballpark, each band as deep as its seats need, for {seats} seats, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the park's plan.", stadiumPitch: "The field lies as OpenStreetMap maps the pitch, and the arrow points north.", stadiumInfield: "The way home plate faces is worked out from the infield OpenStreetMap maps and the building's outline, and the arrow points north.", district: "A plan of {count} real venues at their real positions and orientations, from the outlines OpenStreetMap maps; {mode}.", districtNeighbours: "it is fitted close round the buildings", districtCity: "it keeps the real distances, with a grid of kilometres and a scale", districtCredit: "Credit:", districtOutside: "People outside: {percent} percent of the seats of the venues, {people} people, stand on the sidewalks, plazas and grounds round them, and none on a road.", era: "The markings are those of the {season} season.", hammer: "{name} has the hammer.", ends: "By end, {name}: {list}.", total: "Total", wonPens: "{name} won {a}-{b} on penalties.", pens: "{a}-{b} pens", on: "Drawn as", sep: ", ", stop: ".", colon: ": ", gap: " " },
|
|
203
203
|
budo: {
|
|
204
204
|
sides: { north: "north", south: "south", east: "east", west: "west" },
|
|
205
205
|
judo: "a judo contest area of {contest} by {contest} metres in a safety area {safety} metres wide, laid with tatami of {tatami}",
|
|
@@ -455,7 +455,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
455
455
|
announce: "{name} view", announceAngle: "Turned to {yaw} degrees, seen from {pitch} degrees above the floor.",
|
|
456
456
|
flat: "This browser cannot draw the 3D view, so the flat drawing is shown.",
|
|
457
457
|
standing: "Standing on it, in the 3D view: {list}.", item: "{name} {h}", itemSag: "{name} {h} at the posts, {low} at the middle",
|
|
458
|
-
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", deck: "platform", "cage-fence": "fence", "cage-posts": "padded posts", "ring-ropes": "ropes", "ring-posts": "corner posts", "ribbon-boards": "LED ribbon boards", "hung-board": "centre-hung scoreboard" },
|
|
458
|
+
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", deck: "platform", "cage-fence": "fence", "cage-posts": "padded posts", "ring-ropes": "ropes", "ring-posts": "corner posts", "ribbon-boards": "LED ribbon boards", "hung-board": "centre-hung scoreboard", blocks: "surrounding buildings" },
|
|
459
459
|
},
|
|
460
460
|
rules: {
|
|
461
461
|
"defensive-zone": "Defensive zone",
|
|
@@ -758,7 +758,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
758
758
|
},
|
|
759
759
|
},
|
|
760
760
|
scoreTitle: "{away} {awayScore}、{home} {homeScore}",
|
|
761
|
-
said: { home: "ホーム", away: "アウェイ", won: "{name}の勝ちです。", tied: "同点です。", park: "外野フェンスは、{park}の公表されている距離をもとに描かれています。", officially: "正式名称は{name}です。", ground: "ピッチは、{ground}の公表されている広さをもとに描かれています。", groundDefault: "{ground}のピッチの広さは公表されていないため、国際標準の105×68mで描かれています。", arenaRink: "{arena}のアイスホッケーリンク", arenaCourt: "{arena}のバスケットボールコート", arenaRinkAbout: "アイスホッケーのリンク。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で角が丸く、レッドライン、ブルーライン、フェイスオフサークル、クリース、ゴールがあります", arenaCourtAbout: "バスケットボールのコート。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で、ペイントエリア、スリーポイントライン、フリースローサークル、センターサークルがあります", arenaSize: "広さは、{arena}の公表されている数値をもとに描かれています。", arenaUnknown: "{arena}のリンクはNHLの規格より短かったとされていますが、広さが示されていないため、NHLの規格で描かれています。", real: "スタンドは、OpenStreetMapの建物の輪郭((c) OpenStreetMap contributors)の内側に収まるように、{arena}の{seats}席・{tiers}層を推定して描いたもので、アリーナの設計図ではありません。", realBuilding: "リンクは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", realMapped: "リンクの向きはOpenStreetMapに記録された氷面のとおりで、矢印は北を指します。", realNone: "リンクの向きは分かっていないため、東西に置き、北を上にしています。", stadium: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層のスタンドを推定して描いています。スタジアムの設計図ではありません。", stadiumOpening: "フィールドは屋根の開口部(フィールドと同じ大きさ)に沿って置いており、矢印は北を指します。", stadiumBuilding: "フィールドは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumNone: "フィールドの向きは分かっていないため、東西に置き、北を上にしています。", stadiumHall: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層の客席を推定して描いています。ホールの設計図ではありません。", stadiumRingBuilding: "リングは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumPitchPark: "ホームプレートの向きは、OpenStreetMapに記録されたグラウンドの芝生を、公表されているフェンスの形に合わせて求めており、矢印は北を指します。", stadiumNonePark: "ホームプレートの向きは分かっていないため、建物に収まる向きでグラウンドを描いており、北を示す矢印はありません。", stadiumPark: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席のスタンドを推定して描いています。球場の設計図ではありません。", stadiumPitch: "フィールドの向きはOpenStreetMapに記録されたピッチのとおりで、矢印は北を指します。", stadiumInfield: "ホームプレートの向きは、OpenStreetMapに記録された内野と建物の輪郭から推定しており、矢印は北を指します。", district: "OpenStreetMapの建物の輪郭をもとに、{count}か所の実在の会場を実際の位置と向きのとおりに並べた配置図です。{mode}。", districtNeighbours: "建物のすぐ近くまで寄せて描いています", districtCity: "実際の距離を保ち、1km方眼と縮尺を添えています", districtCredit: "出典:", era: "ラインやマークは{season}シーズンのものです。", hammer: "{name}がハンマー(最後の1投)を持っています。", ends: "{name}のエンドごとの得点:{list}。", total: "合計", wonPens: "{name}がPK戦を{a}-{b}で制しました。", pens: "PK戦 {a}-{b}", on: "図の内容", sep: "、", stop: "。", colon: ":", gap: "" },
|
|
761
|
+
said: { home: "ホーム", away: "アウェイ", won: "{name}の勝ちです。", tied: "同点です。", park: "外野フェンスは、{park}の公表されている距離をもとに描かれています。", officially: "正式名称は{name}です。", ground: "ピッチは、{ground}の公表されている広さをもとに描かれています。", groundDefault: "{ground}のピッチの広さは公表されていないため、国際標準の105×68mで描かれています。", arenaRink: "{arena}のアイスホッケーリンク", arenaCourt: "{arena}のバスケットボールコート", arenaRinkAbout: "アイスホッケーのリンク。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で角が丸く、レッドライン、ブルーライン、フェイスオフサークル、クリース、ゴールがあります", arenaCourtAbout: "バスケットボールのコート。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で、ペイントエリア、スリーポイントライン、フリースローサークル、センターサークルがあります", arenaSize: "広さは、{arena}の公表されている数値をもとに描かれています。", arenaUnknown: "{arena}のリンクはNHLの規格より短かったとされていますが、広さが示されていないため、NHLの規格で描かれています。", real: "スタンドは、OpenStreetMapの建物の輪郭((c) OpenStreetMap contributors)の内側に収まるように、{arena}の{seats}席・{tiers}層を推定して描いたもので、アリーナの設計図ではありません。", realBuilding: "リンクは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", realMapped: "リンクの向きはOpenStreetMapに記録された氷面のとおりで、矢印は北を指します。", realNone: "リンクの向きは分かっていないため、東西に置き、北を上にしています。", stadium: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層のスタンドを推定して描いています。スタジアムの設計図ではありません。", stadiumOpening: "フィールドは屋根の開口部(フィールドと同じ大きさ)に沿って置いており、矢印は北を指します。", stadiumBuilding: "フィールドは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumNone: "フィールドの向きは分かっていないため、東西に置き、北を上にしています。", stadiumHall: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層の客席を推定して描いています。ホールの設計図ではありません。", stadiumRingBuilding: "リングは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumPitchPark: "ホームプレートの向きは、OpenStreetMapに記録されたグラウンドの芝生を、公表されているフェンスの形に合わせて求めており、矢印は北を指します。", stadiumNonePark: "ホームプレートの向きは分かっていないため、建物に収まる向きでグラウンドを描いており、北を示す矢印はありません。", stadiumPark: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席のスタンドを推定して描いています。球場の設計図ではありません。", stadiumPitch: "フィールドの向きはOpenStreetMapに記録されたピッチのとおりで、矢印は北を指します。", stadiumInfield: "ホームプレートの向きは、OpenStreetMapに記録された内野と建物の輪郭から推定しており、矢印は北を指します。", district: "OpenStreetMapの建物の輪郭をもとに、{count}か所の実在の会場を実際の位置と向きのとおりに並べた配置図です。{mode}。", districtNeighbours: "建物のすぐ近くまで寄せて描いています", districtCity: "実際の距離を保ち、1km方眼と縮尺を添えています", districtCredit: "出典:", districtOutside: "会場の外の人出:会場の全座席数の{percent}%にあたる{people}人が、会場の周りの歩道、広場、敷地に立っています。車道には立っていません。", era: "ラインやマークは{season}シーズンのものです。", hammer: "{name}がハンマー(最後の1投)を持っています。", ends: "{name}のエンドごとの得点:{list}。", total: "合計", wonPens: "{name}がPK戦を{a}-{b}で制しました。", pens: "PK戦 {a}-{b}", on: "図の内容", sep: "、", stop: "。", colon: ":", gap: "" },
|
|
762
762
|
budo: {
|
|
763
763
|
sides: { north: "北", south: "南", east: "東", west: "西" },
|
|
764
764
|
judo: "柔道の試合場。試合場内は{contest}m四方、幅{safety}mの安全地帯があり、{tatami}の畳を敷いています",
|
|
@@ -1014,7 +1014,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
1014
1014
|
announce: "{name}の視点", announceAngle: "向きは{yaw}度、見下ろす角度は{pitch}度です。",
|
|
1015
1015
|
flat: "このブラウザーでは3D表示ができないため、平面の図を表示しています。",
|
|
1016
1016
|
standing: "3D表示で立って見えるもの:{list}。", item: "{name} {h}", itemSag: "{name} 支柱の位置で{h}(中央は{low})",
|
|
1017
|
-
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: "審判員席の名札", deck: "台", "cage-fence": "金網", "cage-posts": "パッド付き支柱", "ring-ropes": "ロープ", "ring-posts": "コーナーポスト", "ribbon-boards": "LEDリボンビジョン", "hung-board": "センターハングビジョン(吊り下げ式スコアボード)" },
|
|
1017
|
+
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: "審判員席の名札", deck: "台", "cage-fence": "金網", "cage-posts": "パッド付き支柱", "ring-ropes": "ロープ", "ring-posts": "コーナーポスト", "ribbon-boards": "LEDリボンビジョン", "hung-board": "センターハングビジョン(吊り下げ式スコアボード)", blocks: "周辺の建物" },
|
|
1018
1018
|
},
|
|
1019
1019
|
rules: {
|
|
1020
1020
|
"defensive-zone": "ディフェンディングゾーン",
|
|
@@ -155,6 +155,11 @@ export interface Surface {
|
|
|
155
155
|
};
|
|
156
156
|
/** For a surface with a crowd: the margin it was drawn with, which holds the stands. */
|
|
157
157
|
crowdMargin?: number;
|
|
158
|
+
/** A real stadium of more than one configuration laid out in the venue's own frame: where the middle of its field is in the surface's frame (the surface is shifted to lie there), and the ground, building, stands and crowd under it, in the venue's frame. */
|
|
159
|
+
venue?: {
|
|
160
|
+
shift: [number, number];
|
|
161
|
+
under: string;
|
|
162
|
+
};
|
|
158
163
|
/** Where the parts a situation is drawn against are: a rink's lines and nets, a diamond's bases, a gridiron's goal lines. */
|
|
159
164
|
anchors?: SituationAnchors;
|
|
160
165
|
}
|
package/dist/turn.dom.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type TurnDirection, type TurnPreset, type TurnView } from "./turn.camer
|
|
|
2
2
|
import { type TurnModel, type TurnSettings } from "./turn.model.ts";
|
|
3
3
|
import type { ScoreOptions, SurfaceOptions } from "./types.ts";
|
|
4
4
|
import type { VenueSettings } from "./venue.ts";
|
|
5
|
+
import type { OutsideLevel } from "./outside.paint.ts";
|
|
5
6
|
/** What `mountKyogijoTurn` gives back. `O` is what `update` takes: the options of a drawing, or (for `mountMultiTurn`) of a tournament layout. */
|
|
6
7
|
export interface KyogijoTurn<O = SurfaceOptions | ScoreOptions> {
|
|
7
8
|
/** `3d`, or `flat` where the browser cannot draw the 3D view (or there is nothing to turn): the flat drawing is shown and the rest of this does nothing. */
|
|
@@ -33,6 +34,15 @@ export interface KyogijoTurn<O = SurfaceOptions | ScoreOptions> {
|
|
|
33
34
|
setDirection(direction: TurnDirection): void;
|
|
34
35
|
/** Start the auto-turn if it is stopped and stop it if it is running; it gives whether it is running now. */
|
|
35
36
|
togglePlay(): boolean;
|
|
37
|
+
/** For a view with a crowd outside (a district asked for one): show another level at once, without drawing the view again, so that the turn goes on. */
|
|
38
|
+
setOutside?(level: OutsideLevel): void;
|
|
39
|
+
/** What the crowd outside's last paint cost, in milliseconds, how many people it drew and how (`dots` or `density`), or `undefined` where there is no crowd or it has not been drawn. */
|
|
40
|
+
outsideCost?(): {
|
|
41
|
+
ms: number;
|
|
42
|
+
people: number;
|
|
43
|
+
mode: string;
|
|
44
|
+
pixels: number;
|
|
45
|
+
} | undefined;
|
|
36
46
|
/** The flat drawing that is the floor, or `null` after `destroy`. */
|
|
37
47
|
svg(): SVGSVGElement | null;
|
|
38
48
|
/** Stop listening and empty the container. */
|
package/dist/turn.dom.js
CHANGED
|
@@ -9,6 +9,7 @@ import { controlsMarkup, layoutOf, turnMarkupOf, worldMarkup } from "./turn.mark
|
|
|
9
9
|
import { KYOGIJO_TURN_STYLE } from "./turn.style.js";
|
|
10
10
|
import { esc } from "./format.js";
|
|
11
11
|
import { turnBoardMarkup } from "./overlay.js";
|
|
12
|
+
import { attachOutside3d } from "./outside.dom.js";
|
|
12
13
|
/** True where the page can draw a plane in 3D by one `matrix3d`: that is, nearly everywhere with a stylesheet. */
|
|
13
14
|
export function turnSupported(win = typeof window === "undefined" ? undefined : window) {
|
|
14
15
|
const css = win?.CSS;
|
|
@@ -109,6 +110,14 @@ export function mountTurn(host, options, settings, build) {
|
|
|
109
110
|
faded = elements.map((one) => one.style.opacity);
|
|
110
111
|
};
|
|
111
112
|
gather();
|
|
113
|
+
// The crowd outside, a canvas on the floor (the floor's matrix carries it), painted again only when its level, its detail or its colour changes.
|
|
114
|
+
let outside;
|
|
115
|
+
const crowd = () => {
|
|
116
|
+
outside?.destroy();
|
|
117
|
+
const floor = stage.querySelector(".ky-turn-floor");
|
|
118
|
+
outside = model.outside === undefined || floor === null ? undefined : attachOutside3d(floor, model.outside);
|
|
119
|
+
};
|
|
120
|
+
crowd();
|
|
112
121
|
const buttons = () => root.querySelectorAll("button[data-preset]");
|
|
113
122
|
/** The Play button says what it is and what it does: pressed while the view turns. */
|
|
114
123
|
const syncPlay = () => {
|
|
@@ -127,6 +136,7 @@ export function mountTurn(host, options, settings, build) {
|
|
|
127
136
|
const width = stage.clientWidth || 960;
|
|
128
137
|
const height = stage.clientHeight || 600;
|
|
129
138
|
const layout = layoutOf(model, now, width, height, settings.lens);
|
|
139
|
+
outside?.zoom(now.zoom);
|
|
130
140
|
elements.forEach((one, i) => {
|
|
131
141
|
if (written[i] !== layout.transforms[i])
|
|
132
142
|
one.style.transform = written[i] = layout.transforms[i];
|
|
@@ -477,6 +487,7 @@ export function mountTurn(host, options, settings, build) {
|
|
|
477
487
|
world.setAttribute("data-theme", forced);
|
|
478
488
|
}
|
|
479
489
|
gather();
|
|
490
|
+
crowd();
|
|
480
491
|
// The board is a layer of its own over the view, after the scene: it is written again, and the view's turn is not touched.
|
|
481
492
|
stage.querySelector(".ky-turn-board")?.remove();
|
|
482
493
|
if (model.board !== undefined)
|
|
@@ -517,11 +528,21 @@ export function mountTurn(host, options, settings, build) {
|
|
|
517
528
|
setPlaying(!playing);
|
|
518
529
|
return playing;
|
|
519
530
|
},
|
|
531
|
+
setOutside(level) {
|
|
532
|
+
if (!alive)
|
|
533
|
+
return;
|
|
534
|
+
outside?.setLevel(level);
|
|
535
|
+
},
|
|
536
|
+
outsideCost() {
|
|
537
|
+
const last = outside?.last;
|
|
538
|
+
return last === undefined ? undefined : { ms: last.ms, people: last.stats.people, mode: last.stats.mode, pixels: last.pixels };
|
|
539
|
+
},
|
|
520
540
|
svg: () => (alive ? root.querySelector(".ky-turn-floor svg.kyogijo") : null),
|
|
521
541
|
destroy() {
|
|
522
542
|
if (!alive)
|
|
523
543
|
return;
|
|
524
544
|
alive = false;
|
|
545
|
+
outside?.destroy();
|
|
525
546
|
stop();
|
|
526
547
|
stopTick();
|
|
527
548
|
seen?.disconnect();
|
package/dist/turn.kit.d.ts
CHANGED
|
@@ -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" | "card" | "sign" | "apron" | "bench" | "floor" | "ribbon" | "hung-board";
|
|
4
|
+
export type PartKind = "mast" | "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" | "apron" | "bench" | "floor" | "ribbon" | "hung-board" | "block";
|
|
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`. */
|
|
@@ -35,14 +35,21 @@ export interface Plane {
|
|
|
35
35
|
fadeTo?: number;
|
|
36
36
|
/** How low the eye must be for a `shy` plane to begin to give way: the share (of the plane's side being towards the eye, times the sine of the pitch) at which the fade starts; 0.1 when missing. A low barrier takes a high one, so that it stays whole until the eye is nearly level with it. */
|
|
37
37
|
fadeAt?: number;
|
|
38
|
+
/**
|
|
39
|
+
* A `shy` plane that gives way by the side the eye is on and not by how low it is: every wall on the eye's side of what it surrounds fades the same, at any tilt, and one on the far side stays whole. A see-through building's walls
|
|
40
|
+
* (a raised room is seen through every wall between it and the eye, at a low angle and from straight above alike).
|
|
41
|
+
*/
|
|
42
|
+
fadeSide?: boolean;
|
|
38
43
|
/**
|
|
39
44
|
* 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
|
|
40
45
|
* them the eye is on, and the planes of one building among themselves by depth.
|
|
41
46
|
*/
|
|
42
47
|
group?: number;
|
|
43
48
|
}
|
|
49
|
+
/** The `group` of a plane that belongs to no building of its scene but stands apart from all of them: a district's surrounding blocks. The view stacks these among themselves by depth and then among the buildings' planes by depth, where the buildings' are stacked building by building. */
|
|
50
|
+
export declare const APART = -1;
|
|
44
51
|
/** What stands on the surface as a thing a reader would name: a net, a basket, a goal, a pair of posts. */
|
|
45
|
-
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" | "deck" | "cage-fence" | "cage-posts" | "ring-ropes" | "ring-posts" | "ribbon-boards" | "hung-board";
|
|
52
|
+
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" | "deck" | "cage-fence" | "cage-posts" | "ring-ropes" | "ring-posts" | "ribbon-boards" | "hung-board" | "blocks";
|
|
46
53
|
/** A thing that stands on the surface: where, how high, and the planes it is made of. */
|
|
47
54
|
export interface Upright {
|
|
48
55
|
id: string;
|
|
@@ -102,7 +109,7 @@ export declare function shy(p: Plane, centre: readonly [number, number]): Plane;
|
|
|
102
109
|
* How much of a wall is left (1 whole, down to 0.08) at a view: none is cut away unless the wall is on the eye's side of the surface, and then more the lower the eye is, because a wall seen
|
|
103
110
|
* from straight above hides nothing. `yaw` and `pitch` are the view's, in degrees.
|
|
104
111
|
*/
|
|
105
|
-
export declare function fadeOf(p: Pick<Plane, "shy" | "fadeTo" | "fadeAt">, yaw: number, pitch: number): number;
|
|
112
|
+
export declare function fadeOf(p: Pick<Plane, "shy" | "fadeTo" | "fadeAt" | "fadeSide">, yaw: number, pitch: number): number;
|
|
106
113
|
/** A rectangle on the surface, in feet: its corners. */
|
|
107
114
|
export type Rect = {
|
|
108
115
|
x0: number;
|
package/dist/turn.kit.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
//
|
|
5
5
|
// The frame is the drawing's own: x along the surface and y across it, both in feet, as the SVG has them (y grows downwards on the page), and z the height
|
|
6
6
|
// above the surface, in feet, growing towards the eye of someone looking straight down.
|
|
7
|
+
/** The `group` of a plane that belongs to no building of its scene but stands apart from all of them: a district's surrounding blocks. The view stacks these among themselves by depth and then among the buildings' planes by depth, where the buildings' are stacked building by building. */
|
|
8
|
+
export const APART = -1;
|
|
7
9
|
/** The four numbers a plane's SVG needs: its length along `u` and along `v`, in feet. */
|
|
8
10
|
export const size = (p) => [Math.hypot(...p.u), Math.hypot(...p.v)];
|
|
9
11
|
export const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
@@ -108,6 +110,11 @@ export function fadeOf(p, yaw, pitch) {
|
|
|
108
110
|
const a = (yaw * Math.PI) / 180;
|
|
109
111
|
// The horizontal direction from the surface to the eye (the same sum as the depth of a point).
|
|
110
112
|
const toward = p.shy[0] * Math.sin(a) + p.shy[1] * Math.cos(a);
|
|
113
|
+
// By side: whole on the far side, a short ramp across the walls edge-on to the eye, and the same fade on every wall of the near side, whatever the tilt.
|
|
114
|
+
if (p.fadeSide === true) {
|
|
115
|
+
const t = Math.min(1, Math.max(0, (toward + 0.05) / 0.15));
|
|
116
|
+
return Math.round((1 - (1 - (p.fadeTo ?? 0.08)) * t) * 1000) / 1000;
|
|
117
|
+
}
|
|
111
118
|
const hides = toward * Math.sin((pitch * Math.PI) / 180);
|
|
112
119
|
const from = p.fadeAt ?? 0.1;
|
|
113
120
|
const t = Math.min(1, Math.max(0, (hides - from) / 0.25));
|
package/dist/turn.markup.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// the elements are stacked far to near by `z-index`. A browser that cannot keep nested 3D layers (Safari on a phone can lose them) draws it as it is meant.
|
|
4
4
|
// The same text is what a page is given from `turnMarkup` and what the mount writes; the mount then makes it turn.
|
|
5
5
|
import { TURN_PRESET_NAMES, cameraMatrix, depthOf, eyeDistance, fit, matrix3d, multiply, presetOf, turnDirection } from "./turn.camera.js";
|
|
6
|
-
import { cross, facing, fadeOf, n, size, unit } from "./turn.kit.js";
|
|
6
|
+
import { APART, cross, facing, fadeOf, n, size, unit } from "./turn.kit.js";
|
|
7
7
|
import { buildTurn, turnWords } from "./turn.model.js";
|
|
8
8
|
import { KYOGIJO_TURN_STYLE } from "./turn.style.js";
|
|
9
9
|
import { esc } from "./format.js";
|
|
@@ -71,7 +71,12 @@ export function layoutOf(model, view, width, height, lens) {
|
|
|
71
71
|
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
72
|
// Buildings that stand apart are drawn one after another, the far one first; a plane of none (the floor) goes by its depth alone.
|
|
73
73
|
const rank = model.groups === undefined ? undefined : groupRanks(model.groups, view.yaw);
|
|
74
|
-
|
|
74
|
+
// The planes of the blocks that stand round the buildings (`APART`) are stacked by depth among themselves and then woven into the buildings' by depth, each list keeping its own order.
|
|
75
|
+
const apart = order.filter((one) => one.group === APART).sort((a, b) => a.depth - b.depth);
|
|
76
|
+
const own = order.filter((one) => one.group !== APART).sort((a, b) => (rank === undefined || a.group === undefined || b.group === undefined ? 0 : rank[a.group] - rank[b.group]) || a.depth - b.depth);
|
|
77
|
+
order.length = 0;
|
|
78
|
+
for (let i = 0, j = 0; i < own.length || j < apart.length;)
|
|
79
|
+
order.push(j >= apart.length || (i < own.length && own[i].depth <= apart[j].depth) ? own[i++] : apart[j++]);
|
|
75
80
|
const stack = new Array(model.planes.length + 1).fill(0);
|
|
76
81
|
stack[0] = 1;
|
|
77
82
|
order.forEach((one, rank) => {
|
package/dist/turn.model.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type UprightOptions, type Uprights } from "./uprights.ts";
|
|
|
5
5
|
import { type ResolvedBoard } from "./overlay.ts";
|
|
6
6
|
import type { VenueSettings } from "./venue.ts";
|
|
7
7
|
import type { Box3 } from "./turn.camera.ts";
|
|
8
|
+
import type { OutsideSpec } from "./outside.dom.ts";
|
|
8
9
|
import type { Language, LightingOptions, LightingPreset, ScoreOptions, SurfaceOptions } from "./types.ts";
|
|
9
10
|
/** What can be asked of a 3D view. */
|
|
10
11
|
export interface TurnSettings {
|
|
@@ -112,6 +113,8 @@ export interface TurnModel {
|
|
|
112
113
|
}[];
|
|
113
114
|
/** The board the options ask for (`board`), laid over the view: a layer of its own, never a plane of the scene. */
|
|
114
115
|
board?: ResolvedBoard;
|
|
116
|
+
/** The crowd outside the venues of a district, asked for (`DistrictOptions.outside`): a canvas on the floor that the mount paints (`outside.dom.ts`), never a plane of the scene. */
|
|
117
|
+
outside?: OutsideSpec;
|
|
115
118
|
}
|
|
116
119
|
/** The lighting a venue is built with, as a model's field: the venue's own setting, else the crowd's, and none where there is no venue. */
|
|
117
120
|
export declare const lightingOf: (venue: {
|
package/dist/turn.style.js
CHANGED
|
@@ -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}}
|
|
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}}}
|
|
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}}
|
|
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)}
|
|
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}
|
|
@@ -68,9 +68,20 @@ export const KYOGIJO_TURN_STYLE = `
|
|
|
68
68
|
.ky-turn .u-fascia{fill:var(--ky-up-fascia)}
|
|
69
69
|
.ky-turn .u-shell{fill:var(--ky-up-fascia);stroke:var(--ky-up-roofline);stroke-width:.6}
|
|
70
70
|
.ky-turn .u-ivy{fill:var(--ky-up-ivy)}
|
|
71
|
+
/* The buildings round a district's venues (context.ts): three tones of wall by the way each faces, and a lighter roof. */
|
|
72
|
+
.ky-turn .u-block{stroke:var(--ky-up-blk-line);stroke-width:.5}
|
|
73
|
+
.ky-turn .u-block-a{fill:var(--ky-up-blk-a)}
|
|
74
|
+
.ky-turn .u-block-b{fill:var(--ky-up-blk-b)}
|
|
75
|
+
.ky-turn .u-block-c{fill:var(--ky-up-blk-c)}
|
|
76
|
+
.ky-turn .u-block-roof{fill:var(--ky-up-blk-roof);stroke:var(--ky-up-blk-line);stroke-width:.5;stroke-linejoin:round}
|
|
71
77
|
.ky-turn .u-roof{fill:var(--ky-up-roof);stroke:var(--ky-up-roofline);stroke-width:.8;stroke-linejoin:round}
|
|
72
78
|
.ky-turn .u-signplate{fill:var(--ky-turn-bg);fill-opacity:.9;stroke:var(--ky-turn-rule);stroke-width:.5}
|
|
73
79
|
.ky-turn .u-sign{fill:var(--ky-turn-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);font-weight:600}
|
|
80
|
+
.ky-turn .u-mast{fill:var(--ky-up-fascia);stroke:var(--ky-up-roofline);stroke-width:.4}
|
|
81
|
+
.ky-turn .u-skylight{fill:var(--ky-up-skylight,#a9c8da);fill-opacity:.9;stroke:var(--ky-up-roofline);stroke-width:.5}
|
|
82
|
+
.ky-turn .u-podium{fill:#000;fill-opacity:.16}
|
|
83
|
+
.ky-turn .u-ledge{fill:var(--ky-up-roofline)}
|
|
84
|
+
.ky-turn .u-glazing{fill:#0b1a24;fill-opacity:.32}
|
|
74
85
|
.ky-turn .u-cable{fill:none;stroke:var(--ky-up-roofline);stroke-width:1}
|
|
75
86
|
.ky-turn .u-fabric{fill:var(--ky-up-roof);fill-opacity:.55;stroke:none}
|
|
76
87
|
.ky-turn .u-pod{fill:var(--ky-up-fascia);stroke:var(--ky-up-roofline);stroke-width:.6}
|
|
@@ -120,6 +131,7 @@ export const KYOGIJO_TURN_STYLE = `
|
|
|
120
131
|
.ky-turn .ky-cr-ta{fill:var(--ky-cr-away)}
|
|
121
132
|
.ky-turn .ky-cr-tw{fill:#f2f0ea}
|
|
122
133
|
.ky-turn .ky-cr-aisle{fill:none;stroke:var(--ky-cr-gap);stroke-opacity:.8}
|
|
134
|
+
.ky-turn .ky-cr-shut-veil{fill:var(--ky-cr-empty);fill-opacity:.42}
|
|
123
135
|
.ky-turn .ky-cr-row{fill:none;stroke:var(--ky-cr-gap);stroke-opacity:.5;stroke-width:.5}
|
|
124
136
|
.ky-turn .ky-bud-gseat{stroke:var(--ky-up-rail);stroke-opacity:.3}
|
|
125
137
|
.ky-turn .u-door{fill:var(--ky-up-door)}
|
package/dist/uprights.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// stylesheet. `describeUprights` says the same in words, for a reader who cannot see it.
|
|
4
4
|
import { courtFor, courtGeometry, isNetSport } from "./court.js";
|
|
5
5
|
import { drawingId, feetFence, lengthUnit } from "./common.js";
|
|
6
|
-
import { drawKyogijo, pitchOf } from "./draw.js";
|
|
6
|
+
import { crowdStands, drawKyogijo, pitchOf } from "./draw.js";
|
|
7
7
|
import { rulesFor, footballIn } from "./eras.js";
|
|
8
8
|
import { fieldPlan, isFieldSport } from "./fields.js";
|
|
9
9
|
import { footballLength } from "./geometry.js";
|
|
@@ -126,6 +126,14 @@ export function uprightsFor(o, settings = {}) {
|
|
|
126
126
|
thin = thinnest(sheet.length, sheet.width);
|
|
127
127
|
area = { x0: 0, y0: 0, x1: sheet.length, y1: sheet.width };
|
|
128
128
|
}
|
|
129
|
+
// A real stadium of more than one configuration is drawn in the venue's own frame (the middle of its field at the origin): what stands on the field is moved there with it, and the stands are already in it.
|
|
130
|
+
const shift = crowdStands(o)?.shift;
|
|
131
|
+
if (shift !== undefined) {
|
|
132
|
+
const [dx, dy] = shift;
|
|
133
|
+
items = items.map((item) => ({ ...item, at: [item.at[0] - dx, item.at[1] - dy], planes: item.planes.map((p) => ({ ...p, o: [p.o[0] - dx, p.o[1] - dy, p.o[2]] })) }));
|
|
134
|
+
if (area !== undefined)
|
|
135
|
+
area = { x0: area.x0 - dx, y0: area.y0 - dy, x1: area.x1 - dx, y1: area.y1 - dy };
|
|
136
|
+
}
|
|
129
137
|
// The venue: the stands and the room, raised. A dojo's own walls are the room's, with their doors and windows.
|
|
130
138
|
let defs = "";
|
|
131
139
|
let reach = 0;
|
package/dist/venue.js
CHANGED
|
@@ -50,7 +50,7 @@ export function standsVenueOf(stands, crowd, o, v, lift) {
|
|
|
50
50
|
const density = densityOf(crowd, stands.seats, groundById(o.ground)?.capacity);
|
|
51
51
|
const tifo = { home: stands.sectors.some((s) => s.kind === "tifo-home"), away: stands.sectors.some((s) => s.kind === "tifo-away") };
|
|
52
52
|
const pid = `${v.id}-v`;
|
|
53
|
-
const defs = fine ? patterns(pid, crowd, density, tifo.home || tifo.away ? tifo : undefined, stands.real !== undefined, true) : "";
|
|
53
|
+
const defs = fine ? patterns(pid, crowd, density, tifo.home || tifo.away ? tifo : undefined, stands.real !== undefined, true, stands.rings.some((r) => r.closed === true) || stands.sectors.some((s) => s.kind === "closed")) : "";
|
|
54
54
|
const centre = [stands.box.x + stands.box.w / 2, stands.box.y + stands.box.h / 2];
|
|
55
55
|
const covers = stands.covers;
|
|
56
56
|
const inCover = (p) => covers.some(([x0, y0, x1, y1]) => p[0] >= x0 && p[0] <= x1 && p[1] >= y0 && p[1] <= y1);
|
|
@@ -62,7 +62,11 @@ export function standsVenueOf(stands, crowd, o, v, lift) {
|
|
|
62
62
|
const paint = (ring, piece) => (corners, local) => {
|
|
63
63
|
let svg = `<polygon class="u-bed" points="${pointList(corners)}"/>`;
|
|
64
64
|
const people = ring.fill < 0.95 ? ` fill-opacity="${round(1 - Math.min(0.5, (1 - ring.fill) * 1.6))}"` : "";
|
|
65
|
-
|
|
65
|
+
// Closed seats are the same seats, every one empty.
|
|
66
|
+
if (ring.closed === true)
|
|
67
|
+
svg += fine ? `<polygon fill="url(#${pid}-cr-e)" points="${pointList(corners)}"/><polygon class="ky-cr-shut-veil" points="${pointList(corners)}"/>` : `<polygon class="u-blk" fill-opacity="0.28" points="${pointList(corners)}"/>`;
|
|
68
|
+
else
|
|
69
|
+
svg += fine ? `<polygon fill="url(#${pid}-cr-h)"${people} points="${pointList(corners)}"/>` : `<polygon class="u-blk" fill-opacity="${round(0.28 + 0.6 * density * ring.fill)}" points="${pointList(corners)}"/>`;
|
|
66
70
|
for (const s of stands.sectors) {
|
|
67
71
|
if (s.ring !== ring.id)
|
|
68
72
|
continue;
|
|
@@ -70,7 +74,9 @@ export function standsVenueOf(stands, crowd, o, v, lift) {
|
|
|
70
74
|
if (part.length < 3 || Math.abs(areaOf(part)) < 0.05)
|
|
71
75
|
continue;
|
|
72
76
|
const points = pointList(part.map(local));
|
|
73
|
-
if (s.kind === "
|
|
77
|
+
if (s.kind === "closed")
|
|
78
|
+
svg += fine ? `<polygon class="u-bed" points="${points}"/><polygon fill="url(#${pid}-cr-e)" points="${points}"/><polygon class="ky-cr-shut-veil" points="${points}"/>` : `<polygon class="u-blk" fill-opacity="0.28" points="${points}"/>`;
|
|
79
|
+
else if (s.kind === "away")
|
|
74
80
|
svg += fine ? `<polygon fill="url(#${pid}-cr-a)" points="${points}"/>` : `<polygon class="u-blk-a" fill-opacity="${round(0.3 + 0.6 * density)}" points="${points}"/>`;
|
|
75
81
|
else
|
|
76
82
|
svg += fine ? `<polygon fill="url(#${pid}-cr-${s.kind === "tifo-home" ? "th" : "ta"})" points="${points}"/>` : `<polygon class="${s.kind === "tifo-home" ? "u-th" : "u-ta"}" points="${points}"/>`;
|