@johnmorrisdotca/kyogijo-stadiums 0.39.1 → 0.40.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.
- package/CHANGELOG.md +19 -0
- package/README.md +1 -1
- package/dist/crowd.draw.d.ts +5 -0
- package/dist/crowd.draw.js +10 -8
- package/dist/draw.js +22 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/stadiums.d.ts +6 -1
- package/dist/stadiums.js +12 -3
- package/dist/turn-entry.d.ts +2 -0
- package/dist/turn-entry.js +1 -0
- package/dist/turn.style.js +4 -2
- package/dist/types.d.ts +7 -4
- package/dist/venue.d.ts +5 -0
- package/dist/venue.js +9 -9
- package/dist/venue.lighting.d.ts +48 -6
- package/dist/venue.lighting.js +29 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ All notable changes to this project are written here. The format follows
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.40.1] - 2026-10-21
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Switching a stadium between football and soccer reshaped the whole drawing** (BC Place, Lumen Field): the view was fitted to the field and the bowl was rebuilt from the configuration's seats, so the building's outline, the scale, the frame and the tint of the ground all changed. Now only the field changes. The view is the building's with a little ground round it, about the middle of the field (the same outline and scale for every configuration, in 2D and in 3D), the bowl is the building's and holds the most seats any configuration has (BC Place's soccer 52,497 opens from the lowest bowl up and draws the other 2,003 seats empty, as Lumen Field's does), and the ground round the building is the football field's beige whichever field lies in it (`data-stadium` on the drawing's root; the dark ground is one for every sport). `planStadium(...).total` for BC Place's soccer is now 54,500, the bowl's, with `open` the configuration's 52,497.
|
|
15
|
+
- **The stadiums page's buttons were the browser's own** (the venue buttons) and its field choice differed from the family's: the venues are chips and the field a segmented choice, as on the venues and home pages, 48 pixels high, with a pressed state, in light and dark, and on a phone the chips scroll sideways. A new demo test (`e2e/controls.demo.mjs`) fails on any visible button or select of any demo page that is left in the browser's own style, in light and dark.
|
|
16
|
+
|
|
17
|
+
## [0.40.0] - 2026-10-21
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **The named lighting presets** ([the presets](./docs/venues.md#the-lighting-presets)): `lighting: "game" | "intro" | "concert"` join `"night"` and `"lightsDown"`. `game` is white light with the house half lit (a light veil, `u-veil-half`) and a pool of white light round the surface, `intro` the house dark with the home team's colour washed over the surface and the visitors' (the second colour) in the aisle beams, `concert` the house dark with warm amber beams and a purple surface. The presets are a table of constants, `LIGHT_PRESETS` (each preset's `house`, `aisles`, `pool`, and wash and beam colours), with `LIGHTING_PRESETS` (the names) and `LIGHT_COLORS` (the named colours); `LIGHT_PRESETS`, `LIGHTING_PRESETS`, `LIGHT_COLORS` and `resolveLighting` are exported from the main entry and from `/turn` (the 0.39.0 notes named `LIGHT_COLORS` without exporting it), and `WASH` from `/turn`. The `intro`'s team colours are the package's where it has them: the crowd's `home` and `away`, else the colours of a score's two teams, else blue and amber. `lighting: { preset, colors }` passes colours (the first stands for the home team's, the second for the visitors'), and `wash` and `aisles` win over them, for any preset. `resolveLighting(option, team)` now answers `{ preset, house, aisles, pool, beam, wash }`. `TurnModel.lighting` and the view's `data-lighting` say the preset (the stylesheet has a rule for `game` and one for the three dark ones); in the plan the stands' group has `ky-half` or `ky-down`. The `celebration` (colours chasing round the bowl) is to come. Off by default, and a drawing without lighting is exactly what it was.
|
|
22
|
+
- **A Lighting choice on the demo**, in place of the Night and Lights down buttons (kept in the address as `lighting=intro` and so on, with `wash=` for the colour of light over the preset's own; an old address's `night=1` and `lights=1` still work): a choice in the bar over the venues page's view and the home page's featured arena, and on the stadiums, tournament layouts and district pages. The intro is shown with sample team colours (a red and a blue that are nobody's). In 3D a change goes in while the view turns, without stopping it or sending it back.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **A test that raced another** (`src/docs-language.test.js`): it walks the Markdown files from the root, and met the docs examples' scratch folder `.cache` while that test was removing it, so that `pnpm check` failed now and then with an error reading a file that had just gone. `.cache` is skipped.
|
|
27
|
+
- **The demo's selects were 24 pixels high on a phone in WebKit** (`e2e/looks.demo.mjs`, "every control is a finger wide", failing on `webkit-phone` since 0.39.0): the bar's colour choice took the chip class, whose `display: inline-flex` WebKit does not honour on a select. The bar's selects are `display: inline-block`, 44 pixels high, no wider than 12 rem (they had taken the venue choice's 16 rem minimum).
|
|
28
|
+
|
|
10
29
|
## [0.39.1] - 2026-10-21
|
|
11
30
|
|
|
12
31
|
### Fixed
|
package/README.md
CHANGED
|
@@ -581,7 +581,7 @@ console.log(layout.needs.length, layout.needs.width);
|
|
|
581
581
|
|
|
582
582
|
### The real venues
|
|
583
583
|
|
|
584
|
-
`crowd: { real: true }` draws an NHL arena in its building: the outline from OpenStreetMap, the rink lying along the building where it says so, and a plausible bowl of the seats, tiers and roof the sources give. All thirty-two arenas of the league, division by division ([the real venues](./docs/venues.md)). `boards: true` adds LED ribbon boards and a hung scoreboard, generic content ([LED boards](./docs/venues.md#led-boards)). In 3D the demo's [venues page](https://johnmorrisdotca.github.io/kyogijo-stadiums/venues.html) turns while the pointer is over it, with the stands, walls, roof, LED boards,
|
|
584
|
+
`crowd: { real: true }` draws an NHL arena in its building: the outline from OpenStreetMap, the rink lying along the building where it says so, and a plausible bowl of the seats, tiers and roof the sources give. All thirty-two arenas of the league, division by division ([the real venues](./docs/venues.md)). `boards: true` adds LED ribbon boards and a hung scoreboard, generic content ([LED boards](./docs/venues.md#led-boards)). In 3D the demo's [venues page](https://johnmorrisdotca.github.io/kyogijo-stadiums/venues.html) turns while the pointer is over it, with the stands, walls, roof, LED boards, lighting, building, markings and labels switched on the view while it turns.
|
|
585
585
|
|
|
586
586
|
```ts
|
|
587
587
|
import { drawKyogijo } from "@johnmorrisdotca/kyogijo-stadiums";
|
package/dist/crowd.draw.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export interface DrawInput {
|
|
|
11
11
|
density: number;
|
|
12
12
|
fine: boolean;
|
|
13
13
|
ground: string;
|
|
14
|
+
/** The teams' colours of the drawing's score, where it has them: what a lighting's team colours are (the crowd's own `home` and `away` come first). */
|
|
15
|
+
team?: {
|
|
16
|
+
home?: string;
|
|
17
|
+
away?: string;
|
|
18
|
+
};
|
|
14
19
|
}
|
|
15
20
|
/** The building of a real venue and, where its rink's orientation is known, a small arrow with an N that says where north lies. The N is a path of three strokes, so that no font is needed. */
|
|
16
21
|
export declare function realMarkup(real: RealShell): string;
|
package/dist/crowd.draw.js
CHANGED
|
@@ -197,12 +197,12 @@ export function drawCrowd(stands, o) {
|
|
|
197
197
|
for (const s of stands.sectors)
|
|
198
198
|
body += sector(s, id, fine, density);
|
|
199
199
|
// The house is down: a dark veil over the seats, under the aisles' lights.
|
|
200
|
-
const light = stands.gallery === true ? undefined : resolveLighting(crowd.lighting);
|
|
201
|
-
const night = light?.
|
|
202
|
-
const down = light?.
|
|
200
|
+
const light = stands.gallery === true ? undefined : resolveLighting(crowd.lighting, { home: crowd.home ?? o.team?.home, away: crowd.away ?? o.team?.away });
|
|
201
|
+
const night = light?.aisles === "dots";
|
|
202
|
+
const down = light?.aisles === "beams";
|
|
203
203
|
if (light !== undefined)
|
|
204
204
|
for (const ring of stands.rings)
|
|
205
|
-
body += el("path", { class:
|
|
205
|
+
body += el("path", { class: light.house === "dark" ? "ky-cr-veil ky-cr-veil-down" : light.house === "half" ? "ky-cr-veil ky-cr-veil-half" : "ky-cr-veil", d: path(ring) });
|
|
206
206
|
if (fine) {
|
|
207
207
|
for (const ring of stands.rings) {
|
|
208
208
|
if (stands.gallery === true)
|
|
@@ -211,8 +211,8 @@ export function drawCrowd(stands, o) {
|
|
|
211
211
|
body += rows(ring) + aisles(ring, night);
|
|
212
212
|
else {
|
|
213
213
|
// The lights down: the front of each tier edged with light, each stair aisle a beam from above with its rails (a vomitory a broader one).
|
|
214
|
-
body += rows(ring) + (ring.hole === null ? "" : el("path", { class: "ky-cr-edge", d: outlinePath(ring.hole), fill: "none", stroke: light.
|
|
215
|
-
ring.aisles.forEach(([[x0, y0], [x1, y1]], i) => (body += beamSvg([x0, y0], [x1, y1], light.
|
|
214
|
+
body += rows(ring) + (ring.hole === null ? "" : el("path", { class: "ky-cr-edge", d: outlinePath(ring.hole), fill: "none", stroke: light.beam, "stroke-opacity": 0.6, "stroke-width": 0.6 }));
|
|
215
|
+
ring.aisles.forEach(([[x0, y0], [x1, y1]], i) => (body += beamSvg([x0, y0], [x1, y1], light.beam, "ky-cr-beam", i % 4 === 0)));
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
}
|
|
@@ -242,7 +242,7 @@ export function drawCrowd(stands, o) {
|
|
|
242
242
|
pool += el("radialGradient", { id: `${id}-wash`, cx: 0.5, cy: 0.5, r: 0.72 }, el("stop", { offset: 0, "stop-color": light.wash, "stop-opacity": WASH.middle }) + el("stop", { offset: 1, "stop-color": light.wash, "stop-opacity": WASH.edge }));
|
|
243
243
|
over += el("rect", { class: "ky-cr-wash", x: n1(b.x), y: n1(b.y), width: n1(b.w), height: n1(b.h), rx: stands.sport === "nhl" ? NHL.cornerRadius : undefined, fill: `url(#${id}-wash)` });
|
|
244
244
|
}
|
|
245
|
-
if (
|
|
245
|
+
if (light?.pool === true) {
|
|
246
246
|
const b = stands.box;
|
|
247
247
|
const [w, h] = [b.w + 2 * POOL.reach, b.h + 2 * POOL.reach];
|
|
248
248
|
pool += el("radialGradient", { id: `${id}-pool`, cx: 0.5, cy: 0.5, r: 0.5 }, [[0, 0], [POOL.edge - 0.12, 0], [POOL.edge, POOL.strength], [1, 0]].map(([offset, opacity]) => el("stop", { offset, "stop-color": "#fff4dc", "stop-opacity": opacity })).join(""));
|
|
@@ -254,7 +254,7 @@ export function drawCrowd(stands, o) {
|
|
|
254
254
|
const [x0, y0, x1, y1] = g.box;
|
|
255
255
|
body += el("rect", { class: `ky-cr-${g.kind}`, x: n1(x0), y: n1(y0), width: n1(x1 - x0), height: n1(y1 - y0), rx: g.kind === "bench" ? 1 : undefined });
|
|
256
256
|
}
|
|
257
|
-
return { defs: (fine && (stands.gallery !== true || density > 0) ? patterns(id, crowd, density, tifo.home || tifo.away ? tifo : undefined, stands.gallery === true || stands.real !== undefined) : "") + pool, markup: el("g", { class:
|
|
257
|
+
return { defs: (fine && (stands.gallery !== true || density > 0) ? patterns(id, crowd, density, tifo.home || tifo.away ? tifo : undefined, stands.gallery === true || stands.real !== undefined) : "") + pool, markup: el("g", { class: light === undefined ? "ky-crowd" : light.house === "dark" ? "ky-crowd ky-night ky-down" : light.house === "half" ? "ky-crowd ky-night ky-half" : "ky-crowd ky-night" }, body) + lit, over };
|
|
258
258
|
}
|
|
259
259
|
/** A block of visitors or a card display, a polygon over the band it is in. */
|
|
260
260
|
function sector(s, id, fine, density) {
|
|
@@ -297,6 +297,8 @@ export const KYOGIJO_CROWD_STYLE = `
|
|
|
297
297
|
.kyogijo .ky-night{--ky-cr-bed:#11151a;--ky-cr-empty:#2a313a;--ky-cr-gap:#020304;--ky-cr-conc:#171c22;--ky-cr-rim:#07090c}
|
|
298
298
|
.kyogijo .ky-cr-veil{fill:#04060a;fill-opacity:.6}
|
|
299
299
|
.kyogijo .ky-cr-veil-down{fill-opacity:.8}
|
|
300
|
+
.kyogijo .ky-cr-veil-half{fill-opacity:.3}
|
|
301
|
+
.kyogijo .ky-half{--ky-cr-bed:#2a3039;--ky-cr-empty:#4a535f;--ky-cr-gap:#0a0c0f;--ky-cr-conc:#2a3038}
|
|
300
302
|
.kyogijo .ky-cr-beam,.kyogijo .ky-cr-edge{stroke-linecap:butt}
|
|
301
303
|
.kyogijo .ky-night .ky-cr-aisle{stroke:#ffd9a3;stroke-opacity:.95;stroke-dasharray:1.1 3.4;stroke-linecap:round}
|
|
302
304
|
.kyogijo .ky-night .ky-cr-vom{stroke:#7dffb0;stroke-opacity:.6;stroke-dasharray:none}
|
package/dist/draw.js
CHANGED
|
@@ -11,7 +11,7 @@ import { FOOTBALL } from "./geometry.js";
|
|
|
11
11
|
import { renderLayers } from "./layers.js";
|
|
12
12
|
import { arenaById, arenaCourtIn, arenaNameIn, arenaOfficialName, arenaRinkIn } from "./arenas.js";
|
|
13
13
|
import { venueById, venueBowl } from "./venues.js";
|
|
14
|
-
import { planPark, stadiumBowl, stadiumById, stadiumConfigOf, stadiumKind, stadiumNames, stadiumSeats } from "./stadiums.js";
|
|
14
|
+
import { KYOGIJO_STADIUM_STYLE, planPark, stadiumBowl, stadiumById, stadiumConfigOf, stadiumConfigs, stadiumKind, stadiumNames, stadiumSeats } from "./stadiums.js";
|
|
15
15
|
import { groundById, groundNameIn, groundOfficialName } from "./grounds.js";
|
|
16
16
|
import { BOARD, courtFor, hasSetsBoard, isNetLeague, isNetSport } from "./court.js";
|
|
17
17
|
import { cricketBoardMarkup } from "./cricket.board.js";
|
|
@@ -61,6 +61,7 @@ import { overlayMarkup, placeBoard, resolveBoard } from "./overlay.js";
|
|
|
61
61
|
import { KYOGIJO_HARDWOOD_STYLE, resolveHardwood } from "./hardwood.js";
|
|
62
62
|
import { WALL_SEATS, densityOf, layoutOf as crowdLayout, resolveCrowd } from "./crowd.js";
|
|
63
63
|
import { drawCrowd, KYOGIJO_CROWD_STYLE } from "./crowd.draw.js";
|
|
64
|
+
import { teamColors } from "./venue.lighting.js";
|
|
64
65
|
import { parkLayout } from "./crowd.park.js";
|
|
65
66
|
import { featureColours, featureStyle } from "./features.js";
|
|
66
67
|
import { isLook, lookStyle } from "./look.js";
|
|
@@ -468,6 +469,13 @@ const smallDrawing = (o) => {
|
|
|
468
469
|
const w = typeof o.width === "number" ? o.width : typeof o.width === "string" && /^\s*\d+(?:\.\d+)?(?:px)?\s*$/.test(o.width) ? parseFloat(o.width) : undefined;
|
|
469
470
|
return o.detail === "simple" || (w !== undefined && w < 480);
|
|
470
471
|
};
|
|
472
|
+
/** The ground, in feet, a stadium's frame holds beyond its building. */
|
|
473
|
+
const VENUE_PAD = 6;
|
|
474
|
+
/** Whether the options draw a real stadium of more than one configuration (a football field and a soccer pitch in one building): its frame is the building's, not the field's. */
|
|
475
|
+
function stadiumHasConfigs(o, crowd) {
|
|
476
|
+
const real = realStadiumOf(o, chooseLeague(o).sport, crowd.real);
|
|
477
|
+
return real !== undefined && stadiumKind(real.stadium) === "stadium" && stadiumConfigs(real.stadium).length > 1;
|
|
478
|
+
}
|
|
471
479
|
/** A surface with the stands and the crowd round it: the ground is made as wide as the stands need, then the stands are laid on it under the surface. */
|
|
472
480
|
function buildCrowded(o, portrait, id, score, scored, extra, crowd) {
|
|
473
481
|
let margin = extra.margin;
|
|
@@ -482,6 +490,13 @@ function buildCrowded(o, portrait, id, score, scored, extra, crowd) {
|
|
|
482
490
|
}
|
|
483
491
|
const surface = buildWhole(o, portrait, id, score, scored, { ...extra, margin });
|
|
484
492
|
const stands = standsFor(o, surface, crowd);
|
|
493
|
+
// A stadium that stages more than one field is framed by its building, once, whichever field is drawn: the view is the building's with a small margin, about the middle of the field.
|
|
494
|
+
const field = stands.box;
|
|
495
|
+
if (surface.turn === undefined && o.range === undefined && stadiumHasConfigs(o, crowd)) {
|
|
496
|
+
const [cx, cy] = [field.x + field.w / 2, field.y + field.h / 2];
|
|
497
|
+
const [left, right, top, bottom] = [field.w / 2 + stands.reach.left, field.w / 2 + stands.reach.right, field.h / 2 + stands.reach.top, field.h / 2 + stands.reach.bottom].map((d) => d + VENUE_PAD);
|
|
498
|
+
surface.view = { x: cx - left, y: cy - top, w: left + right, h: top + bottom };
|
|
499
|
+
}
|
|
485
500
|
const seats = stands.seats;
|
|
486
501
|
const ground = groundById(o.ground)?.capacity;
|
|
487
502
|
const fine = crowd.detail === "fine" || (crowd.detail === "auto" && !smallDrawing(o));
|
|
@@ -489,7 +504,7 @@ function buildCrowded(o, portrait, id, score, scored, extra, crowd) {
|
|
|
489
504
|
const bg = /class="([^"]*)"/.exec(first)?.[1] ?? "ky-apron";
|
|
490
505
|
// A gallery is empty unless the options say how full: its seats are the detail.
|
|
491
506
|
const taken = stands.gallery === true && crowd.density === undefined && crowd.attendance === undefined ? 0 : densityOf(crowd, seats, ground);
|
|
492
|
-
const drawn = drawCrowd(stands, { id, crowd, density: taken, fine, ground: bg });
|
|
507
|
+
const drawn = drawCrowd(stands, { id, crowd, density: taken, fine, ground: bg, team: teamColors(o) });
|
|
493
508
|
surface.base = first + drawn.markup + surface.base.slice(first.length);
|
|
494
509
|
surface.defs += drawn.defs;
|
|
495
510
|
surface.lines += drawn.over;
|
|
@@ -929,6 +944,9 @@ function drawnKyogijo(o) {
|
|
|
929
944
|
const walled = sport === "mlb" && ballparkDrawn(ballparkFor(o));
|
|
930
945
|
const hardwood = sport === "nba" && o.court?.hardwood !== undefined && o.court.hardwood !== false;
|
|
931
946
|
const crowded = crowdOf(o) !== undefined;
|
|
947
|
+
// The stadium a drawing is in, where it has more than one configuration: its ground is its own, whichever field lies in it (the stylesheet reads it).
|
|
948
|
+
const crowdHere = crowdOf(o);
|
|
949
|
+
const stadiumOfDrawing = crowdHere !== undefined && stadiumHasConfigs(o, crowdHere) ? realStadiumOf(o, sport, crowdHere.real)?.stadium.id : undefined;
|
|
932
950
|
const style = colourStyle(o, { left: leftTeam, right: rightTeam, home: isScore(o) ? o.home : undefined, away: isScore(o) ? o.away : undefined }, featured);
|
|
933
951
|
const palette = isLook(o.look) ? o.look : undefined;
|
|
934
952
|
const budoLook = isMatSport(sport) ? budoLookOf(sport, o.budo?.look ?? (sport === "dojo" ? dojoSpecOf(o.dojo).look : undefined)) : undefined;
|
|
@@ -1121,6 +1139,7 @@ function drawnKyogijo(o) {
|
|
|
1121
1139
|
role: "img",
|
|
1122
1140
|
"aria-labelledby": `${titleId} ${descId}`,
|
|
1123
1141
|
"data-sport": sport,
|
|
1142
|
+
"data-stadium": stadiumOfDrawing,
|
|
1124
1143
|
"data-league": o.league === undefined || o.league === "" ? (chooseLeague(o).league?.id ?? undefined) : o.league,
|
|
1125
1144
|
"data-range": isScore(o) && sport === "curling" ? undefined : rangeLabel(o),
|
|
1126
1145
|
"data-event": o.host !== undefined && typeof o.event === "string" ? safeClass(o.event) : undefined,
|
|
@@ -1139,7 +1158,7 @@ function drawnKyogijo(o) {
|
|
|
1139
1158
|
part: parts ? "svg" : undefined,
|
|
1140
1159
|
style: style === "" ? undefined : style,
|
|
1141
1160
|
}, `<title id="${titleId}">${esc(title)}</title><desc id="${descId}">${esc(description)}</desc>` +
|
|
1142
|
-
(o.style === "none" ? "" : `<style>${KYOGIJO_BASE_STYLE}${left?.round === true || right?.round === true || (pg !== undefined && pageant?.style === "stage") || (pg !== undefined && pageant?.emblem?.shape !== "plain" && pageant?.emblem !== undefined) ? `\n${KYOGIJO_MARK_STYLE}` : ""}${pg === undefined ? "" : `\n${KYOGIJO_PAGEANT_STYLE}${pageant?.animate === undefined || pageant.animate === "none" ? "" : `\n${KYOGIJO_PAGEANT_MOTION}`}`}${sport === "curling" ? `\n${KYOGIJO_CURLING_STYLE}` : ""}${foldedSheet ? `\n${KYOGIJO_FOLD_STYLE}` : ""}${isPoolSport(sport) ? `\n${o.pool?.venue !== undefined ? KYOGIJO_POOL_STYLE : poolStyle({ sport, ...commonOf(o.pool ?? {}), race: o.pool?.race !== undefined, section: o.pool?.section !== undefined && o.pool.section !== false })}` : ""}${plan === undefined ? "" : `\n${courtStyle(plan.sport, plan.look)}`}${fieldsStyle(sport) === "" ? "" : `\n${fieldsStyle(sport)}`}${athleticsStyle(sport) === "" ? "" : `\n${athleticsStyle(sport)}`}${skatingStyle(sport) === "" ? "" : `\n${skatingStyle(sport)}`}${budoLook === undefined ? "" : `\n${budoStyle(sport, budoLook)}`}${palette === undefined ? "" : `\n${lookStyle(palette)}`}${athleticsTrackRules(sport, o.athletics?.track) === "" ? "" : `\n${athleticsTrackRules(sport, o.athletics?.track)}`}${walled ? `\n${KYOGIJO_WALLS_STYLE}` : ""}${hardwood ? `\n${KYOGIJO_HARDWOOD_STYLE}` : ""}${features === undefined ? "" : `\n${featureStyle(sport, features)}`}${notes === undefined || notes.svg === "" ? "" : `\n${KYOGIJO_NOTE_STYLE}`}${grounded ? `\n${KYOGIJO_BACKGROUND_STYLE}` : ""}${crowded ? `\n${KYOGIJO_CROWD_STYLE}` : ""}${crowded && sport === "dojo" ? KYOGIJO_BUDO_GALLERY_STYLE : ""}${situation === undefined ? "" : `\n${KYOGIJO_SITUATION_STYLE}`}${o.host === undefined ? "" : `\n${specialStyle(hostSport(o.host))}`}</style>`) +
|
|
1161
|
+
(o.style === "none" ? "" : `<style>${KYOGIJO_BASE_STYLE}${left?.round === true || right?.round === true || (pg !== undefined && pageant?.style === "stage") || (pg !== undefined && pageant?.emblem?.shape !== "plain" && pageant?.emblem !== undefined) ? `\n${KYOGIJO_MARK_STYLE}` : ""}${pg === undefined ? "" : `\n${KYOGIJO_PAGEANT_STYLE}${pageant?.animate === undefined || pageant.animate === "none" ? "" : `\n${KYOGIJO_PAGEANT_MOTION}`}`}${sport === "curling" ? `\n${KYOGIJO_CURLING_STYLE}` : ""}${foldedSheet ? `\n${KYOGIJO_FOLD_STYLE}` : ""}${isPoolSport(sport) ? `\n${o.pool?.venue !== undefined ? KYOGIJO_POOL_STYLE : poolStyle({ sport, ...commonOf(o.pool ?? {}), race: o.pool?.race !== undefined, section: o.pool?.section !== undefined && o.pool.section !== false })}` : ""}${plan === undefined ? "" : `\n${courtStyle(plan.sport, plan.look)}`}${fieldsStyle(sport) === "" ? "" : `\n${fieldsStyle(sport)}`}${athleticsStyle(sport) === "" ? "" : `\n${athleticsStyle(sport)}`}${skatingStyle(sport) === "" ? "" : `\n${skatingStyle(sport)}`}${budoLook === undefined ? "" : `\n${budoStyle(sport, budoLook)}`}${palette === undefined ? "" : `\n${lookStyle(palette)}`}${athleticsTrackRules(sport, o.athletics?.track) === "" ? "" : `\n${athleticsTrackRules(sport, o.athletics?.track)}`}${walled ? `\n${KYOGIJO_WALLS_STYLE}` : ""}${hardwood ? `\n${KYOGIJO_HARDWOOD_STYLE}` : ""}${features === undefined ? "" : `\n${featureStyle(sport, features)}`}${notes === undefined || notes.svg === "" ? "" : `\n${KYOGIJO_NOTE_STYLE}`}${grounded ? `\n${KYOGIJO_BACKGROUND_STYLE}` : ""}${stadiumOfDrawing === undefined ? "" : `\n${KYOGIJO_STADIUM_STYLE}`}${crowded ? `\n${KYOGIJO_CROWD_STYLE}` : ""}${crowded && sport === "dojo" ? KYOGIJO_BUDO_GALLERY_STYLE : ""}${situation === undefined ? "" : `\n${KYOGIJO_SITUATION_STYLE}`}${o.host === undefined ? "" : `\n${specialStyle(hostSport(o.host))}`}</style>`) +
|
|
1143
1162
|
(defs === "" ? "" : `<defs>${defs}</defs>`) +
|
|
1144
1163
|
body);
|
|
1145
1164
|
return root;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { HOME_FIRST, HOME_FIRST_LEAGUES, homeSideFor } from "./homeside.ts";
|
|
|
3
3
|
export { inkOn, luminance, rgbOf, safeColour, safePicture } from "./colour.ts";
|
|
4
4
|
export { safeLogo } from "./logo.ts";
|
|
5
5
|
export { useEmblems } from "./emblems-source.ts";
|
|
6
|
+
export { LIGHTING_PRESETS, LIGHT_COLORS, LIGHT_PRESETS, resolveLighting } from "./venue.lighting.ts";
|
|
6
7
|
export type { EmblemDrawOptions, EmblemId, EmblemRef, EmblemRenderer, EmblemStyleName } from "./emblems-source.ts";
|
|
7
8
|
export { useFlags } from "./flags-source.ts";
|
|
8
9
|
export type { FlagRenderer, FlagRequest, FlagShape } from "./flags-source.ts";
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { HOME_FIRST, HOME_FIRST_LEAGUES, homeSideFor } from "./homeside.js";
|
|
|
4
4
|
export { inkOn, luminance, rgbOf, safeColour, safePicture } from "./colour.js";
|
|
5
5
|
export { safeLogo } from "./logo.js";
|
|
6
6
|
export { useEmblems } from "./emblems-source.js";
|
|
7
|
+
export { LIGHTING_PRESETS, LIGHT_COLORS, LIGHT_PRESETS, resolveLighting } from "./venue.lighting.js";
|
|
7
8
|
export { useFlags } from "./flags-source.js";
|
|
8
9
|
export { OUTCOMES, drawLayers, layerTable } from "./layers.js";
|
|
9
10
|
export { RANGES } from "./ranges.names.js";
|
package/dist/stadiums.d.ts
CHANGED
|
@@ -181,6 +181,11 @@ export declare const stadiumOrientation: (stadium: RealStadium) => StadiumOrient
|
|
|
181
181
|
export declare const stadiumConfigOf: (sport: string | undefined) => StadiumConfigId | undefined;
|
|
182
182
|
/** The seats of a stadium for a configuration (0 for one it has none of). */
|
|
183
183
|
export declare const stadiumSeats: (stadium: RealStadium, config: StadiumConfigId) => number;
|
|
184
|
+
/**
|
|
185
|
+
* 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. Light only: the dark ground is one for every sport.
|
|
187
|
+
*/
|
|
188
|
+
export declare const KYOGIJO_STADIUM_STYLE: string;
|
|
184
189
|
/** 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). */
|
|
185
190
|
export declare const HALL_FIRST_ROW = 8;
|
|
186
191
|
/** The height of the first row above the field, in feet, the rise of a bowl's rows by the number of bowls (what the lowest, a middle and the upper rise, at most), and the rise of the walk between two bowls (a suite level): ours, no source gives them. */
|
|
@@ -225,7 +230,7 @@ export interface StadiumPlan {
|
|
|
225
230
|
* import { planStadium, stadiumById } from "@johnmorrisdotca/kyogijo-stadiums/stadiums";
|
|
226
231
|
* const plan = planStadium(stadiumById("bc-place")!, "soccer", { x: 0, y: 0, w: 344.5, h: 223.1 });
|
|
227
232
|
* console.log(plan.orientation.basis, plan.seats.map((t) => `${t.tier} ${t.seats}`).join(", "), plan.total);
|
|
228
|
-
* // opening lower 27695, upper
|
|
233
|
+
* // opening lower 27695, upper 26805 54500
|
|
229
234
|
* ```
|
|
230
235
|
*/
|
|
231
236
|
export declare function planStadium(stadium: RealStadium, config: StadiumConfigId, box: Box): StadiumPlan;
|
package/dist/stadiums.js
CHANGED
|
@@ -59,6 +59,12 @@ export const stadiumOrientation = (stadium) => ({ heading: stadium.osm.axis.head
|
|
|
59
59
|
export const stadiumConfigOf = (sport) => (sport === "cfl" || sport === "nfl" || sport === "soccer" || sport === "mlb" || sport === "boxing" ? sport : undefined);
|
|
60
60
|
/** The seats of a stadium for a configuration (0 for one it has none of). */
|
|
61
61
|
export const stadiumSeats = (stadium, config) => stadium.configs[config]?.seats ?? 0;
|
|
62
|
+
/**
|
|
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. Light only: the dark ground is one for every sport.
|
|
65
|
+
*/
|
|
66
|
+
export const KYOGIJO_STADIUM_STYLE = "@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
|
+
":where(html[data-theme=light] .kyogijo[data-sport=soccer][data-stadium],.kyogijo[data-theme=light][data-sport=soccer][data-stadium]){--ky-apron:#dcd8c9}";
|
|
62
68
|
// ---- The bowl. ----
|
|
63
69
|
/** 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. */
|
|
64
70
|
const FIRST_ROW = { cfl: { side: 42, end: 28, corner: 60 }, nfl: { side: 40, end: 26, corner: 60 } };
|
|
@@ -105,7 +111,7 @@ const PLANS = new WeakMap();
|
|
|
105
111
|
* import { planStadium, stadiumById } from "@johnmorrisdotca/kyogijo-stadiums/stadiums";
|
|
106
112
|
* const plan = planStadium(stadiumById("bc-place")!, "soccer", { x: 0, y: 0, w: 344.5, h: 223.1 });
|
|
107
113
|
* console.log(plan.orientation.basis, plan.seats.map((t) => `${t.tier} ${t.seats}`).join(", "), plan.total);
|
|
108
|
-
* // opening lower 27695, upper
|
|
114
|
+
* // opening lower 27695, upper 26805 54500
|
|
109
115
|
* ```
|
|
110
116
|
*/
|
|
111
117
|
export function planStadium(stadium, config, box) {
|
|
@@ -133,6 +139,8 @@ function plan(stadium, config, rink) {
|
|
|
133
139
|
const place = (ring) => ring.map(([x, y]) => [round((x - middle[0]) * FEET_PER_METRE + at[0], 2), round((y - middle[1]) * FEET_PER_METRE + at[1], 2)]);
|
|
134
140
|
const footprint = place(outline);
|
|
135
141
|
const fb = boxOf(footprint);
|
|
142
|
+
// How far the building reaches from the middle of the field on each side, worked out from the outline as it is (not as placed and rounded): one answer for every configuration, which is the stadium's frame.
|
|
143
|
+
const around = boxOf(outline.map(([x, y]) => [(x - middle[0]) * FEET_PER_METRE, (y - middle[1]) * FEET_PER_METRE]));
|
|
136
144
|
const openingRing = stadium.osm.roof?.opening?.ring;
|
|
137
145
|
const opening = openingRing === undefined ? undefined : place(turned(openingRing));
|
|
138
146
|
// The first row round a football field's footprint, and the largest bowl-shaped outline that fits inside the building round the middle of the field.
|
|
@@ -163,7 +171,8 @@ function plan(stadium, config, rink) {
|
|
|
163
171
|
const depth = (first.x0 - outer.x0) + (first.y0 - outer.y0) + (outer.x1 - first.x1) + (outer.y1 - first.y1);
|
|
164
172
|
const gap = GAP / Math.max(20, depth / 4);
|
|
165
173
|
const open = stadium.configs[config]?.seats ?? 0;
|
|
166
|
-
|
|
174
|
+
// The bowl is the building's, the same for every configuration: it holds the most seats any of them opens (or the seats the data gives the bowl), and a configuration that opens fewer closes the rest.
|
|
175
|
+
const seatsTotal = stadium.bowlSeats ?? Math.max(...Object.values(stadium.configs).map((c) => c?.seats ?? 0));
|
|
167
176
|
const count = Math.min(3, stadium.tiers.count);
|
|
168
177
|
const lower = stadium.tiers.lowerSeats === undefined ? (count === 1 ? 1 : count === 2 ? LOWER_SHARE : 0.36) : stadium.tiers.lowerSeats / seatsTotal;
|
|
169
178
|
const shares = count === 1 ? [1] : count === 2 ? [lower, 1 - lower] : [lower, (1 - lower) * 0.36, (1 - lower) * 0.64];
|
|
@@ -256,7 +265,7 @@ function plan(stadium, config, rink) {
|
|
|
256
265
|
const bowl = {
|
|
257
266
|
rings,
|
|
258
267
|
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 },
|
|
259
|
-
reach: { left: Math.max(0, rink.
|
|
268
|
+
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) },
|
|
260
269
|
shell: {
|
|
261
270
|
footprint,
|
|
262
271
|
hall: { ...outer, x0: outer.x0 - HALL, y0: outer.y0 - HALL, x1: outer.x1 + HALL, y1: outer.y1 + HALL, r: outer.r + HALL },
|
package/dist/turn-entry.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type { Box3, Mat4, Placement, TurnPreset, TurnToward, TurnView } from "./
|
|
|
12
12
|
export { describeUprights, heightUnit, planesOf, uprightsFor } from "./uprights.ts";
|
|
13
13
|
export type { UprightOptions, Uprights } from "./uprights.ts";
|
|
14
14
|
export { ROOF } from "./venue.ts";
|
|
15
|
+
export { LIGHTING_PRESETS, LIGHT_COLORS, LIGHT_PRESETS, WASH, resolveLighting } from "./venue.lighting.ts";
|
|
16
|
+
export type { House, LightPreset, Lighting } from "./venue.lighting.ts";
|
|
15
17
|
export type { Venue, VenueBuild, VenueSettings } from "./venue.ts";
|
|
16
18
|
export type { LedBoardOptions, LedBoardPattern, LightingOptions, LightingPreset } from "./types.ts";
|
|
17
19
|
export type { Plane, PartKind, Upright, UprightKind, V3 } from "./turn.kit.ts";
|
package/dist/turn-entry.js
CHANGED
|
@@ -7,3 +7,4 @@ export { KYOGIJO_TURN_STYLE } from "./turn.style.js";
|
|
|
7
7
|
export { CURLING_PRESETS, LONG_PRESETS, NET_PRESETS, PITCH_MAX, STEP, TURN_DEFAULT, TURN_PRESETS, TURN_PRESET_NAMES, ZOOM_MAX, ZOOM_MIN, cameraMatrix, clampView, depthOf, drag, eyeDistance, fit, matrix3d, multiply, nudge, presetOf, presetView, presetsFor, project, step } from "./turn.camera.js";
|
|
8
8
|
export { describeUprights, heightUnit, planesOf, uprightsFor } from "./uprights.js";
|
|
9
9
|
export { ROOF } from "./venue.js";
|
|
10
|
+
export { LIGHTING_PRESETS, LIGHT_COLORS, LIGHT_PRESETS, WASH, resolveLighting } from "./venue.lighting.js";
|
package/dist/turn.style.js
CHANGED
|
@@ -93,11 +93,13 @@ export const KYOGIJO_TURN_STYLE = `
|
|
|
93
93
|
.ky-turn[data-lighting]{--ky-cr-bed:#11151a;--ky-cr-empty:#2a313a;--ky-cr-gap:#020304;--ky-cr-conc:#171c22;--ky-cr-dim:1;--ky-up-fascia:#272c29;--ky-up-ground:#1d211e;--ky-night-aisle:#ffd9a3;--ky-night-exit:#7dffb0;--ky-night-rim:#ffe0b0}
|
|
94
94
|
.ky-turn .u-veil{fill:#04060a;fill-opacity:.62}
|
|
95
95
|
.ky-turn .u-veil-down{fill-opacity:.8}
|
|
96
|
-
.ky-turn
|
|
96
|
+
.ky-turn .u-veil-half{fill-opacity:.3}
|
|
97
|
+
.ky-turn:is([data-lighting=lightsDown],[data-lighting=intro],[data-lighting=concert]){--ky-cr-bed:#0a0d11;--ky-cr-conc:#101419;--ky-up-fascia:#1b1f1d;--ky-up-ground:#15181a}
|
|
98
|
+
.ky-turn[data-lighting=game]{--ky-cr-bed:#2a3039;--ky-cr-empty:#4a535f;--ky-cr-gap:#0a0c0f;--ky-cr-conc:#2a3038;--ky-up-fascia:#3b403c;--ky-up-ground:#242823}
|
|
97
99
|
.ky-turn[data-lighting=night] .ky-cr-aisle{stroke:var(--ky-night-aisle);stroke-opacity:.95;stroke-dasharray:1.1 3.4;stroke-linecap:round}
|
|
98
100
|
.ky-turn[data-lighting=night] .ky-cr-vom{stroke:var(--ky-night-exit);stroke-opacity:.6;stroke-dasharray:none}
|
|
99
101
|
.ky-turn[data-lighting] .u-suite-win{fill:color-mix(in srgb,var(--ky-night-aisle) 50%,#2a2218)}
|
|
100
|
-
.ky-turn[data-lighting=lightsDown] .u-suite-win{fill:color-mix(in srgb,var(--ky-night-aisle) 10%,#0d1116)}
|
|
102
|
+
.ky-turn:is([data-lighting=lightsDown],[data-lighting=intro],[data-lighting=concert]) .u-suite-win{fill:color-mix(in srgb,var(--ky-night-aisle) 10%,#0d1116)}
|
|
101
103
|
.ky-turn[data-lighting] .u-coping{fill:color-mix(in srgb,var(--ky-night-rim) 55%,var(--ky-up-fascia))}
|
|
102
104
|
.ky-turn[data-lighting] .u-suite-ledge{fill:color-mix(in srgb,var(--ky-night-rim) 45%,var(--ky-up-fascia))}
|
|
103
105
|
.ky-turn[data-lighting] .u-clane{stroke:var(--ky-night-rim);stroke-opacity:.4}
|
package/dist/types.d.ts
CHANGED
|
@@ -275,13 +275,16 @@ export type SheetEnd = Side | "top" | "bottom";
|
|
|
275
275
|
/** The shape of a venue's stands round the playing surface. */
|
|
276
276
|
export type CrowdShape = "bowl" | "sides" | "horseshoe" | "grandstand";
|
|
277
277
|
/**
|
|
278
|
-
* The lighting of a venue (`crowd.lighting` in the plan, `venue.lighting` in the 3D view)
|
|
279
|
-
* boards and the scoreboard glowing, the exits and the concourse rim softly lit. `"lightsDown"` is a show's house: nearly black, each stair aisle lit from
|
|
280
|
-
*
|
|
278
|
+
* The lighting of a venue (`crowd.lighting` in the plan, `venue.lighting` in the 3D view): a named preset (`LIGHT_PRESETS`), `true` (the night), or `{ preset, colors, aisles, wash }`. `"night"` dims the house, lights the playing surface and shows small
|
|
279
|
+
* lights along the aisles and stairs, the ribbon boards and the scoreboard glowing, the exits and the concourse rim softly lit. `"game"` is white light with the house half lit. `"lightsDown"` is a show's house: nearly black, each stair aisle lit from
|
|
280
|
+
* above as a beam with a soft falloff on the steps either side, the rails and the front of every step edged with light. `"intro"` is the same house with the home team's colour washed over the surface and the visitors' in the beams (the teams'
|
|
281
|
+
* colours where the options have them, else blue and amber); `"concert"` has warm amber beams and a purple surface. `colors` are one or two colours that stand for the home and the visitors (before the options' teams); `wash` colours the playing surface
|
|
282
|
+
* with a light (a CSS colour, or `white`, `amber`, `purple`, `magenta`, `blue`, `cyan`; the markings stay visible) and `aisles` the beams, and both win over the preset's. `false` is no lighting. Off by default.
|
|
281
283
|
*/
|
|
282
|
-
export type LightingPreset = "night" | "lightsDown";
|
|
284
|
+
export type LightingPreset = "night" | "game" | "lightsDown" | "intro" | "concert";
|
|
283
285
|
export type LightingOptions = LightingPreset | boolean | {
|
|
284
286
|
preset?: LightingPreset;
|
|
287
|
+
colors?: readonly string[];
|
|
285
288
|
aisles?: string;
|
|
286
289
|
wash?: string;
|
|
287
290
|
};
|
package/dist/venue.d.ts
CHANGED
|
@@ -30,6 +30,11 @@ export interface VenueBuild extends VenueSettings {
|
|
|
30
30
|
id: string;
|
|
31
31
|
/** Leave the scoreboard hung over the middle out (a tournament hall has scoreboards of its own). */
|
|
32
32
|
noCentre?: boolean;
|
|
33
|
+
/** The teams' colours the drawing has (a crowd's `home` and `away`, or a score's teams'): what a lighting's team colours are. */
|
|
34
|
+
team?: {
|
|
35
|
+
home?: string;
|
|
36
|
+
away?: string;
|
|
37
|
+
};
|
|
33
38
|
}
|
|
34
39
|
/** What the venue is made of: the things that stand, and the patterns they are drawn with (the contents of a `defs`). */
|
|
35
40
|
export interface Venue {
|
package/dist/venue.js
CHANGED
|
@@ -10,7 +10,7 @@ import { groundById } from "./grounds.js";
|
|
|
10
10
|
import { ARC_STEPS, clipAround, clipConvex, clipSegment, areaOf, pointList, raked, slopeOf, stripsOf, tidy } from "./venue.geom.js";
|
|
11
11
|
import { APRON_RISE, apronPlanes, backEdges, backWall, concourseFloor, rimAt, suiteWall } from "./venue.fill.js";
|
|
12
12
|
import { firstHit, realBuilding } from "./venue.real.js";
|
|
13
|
-
import { beamSvg, edgeSvg, lightPool, lightWash, resolveLighting } from "./venue.lighting.js";
|
|
13
|
+
import { beamSvg, edgeSvg, lightPool, lightWash, resolveLighting, teamColors } from "./venue.lighting.js";
|
|
14
14
|
import { centreBoard, fasciaRibbon, hangsOver, ledStrip, resolveBoards, ribbonHigh, ribbonShare, tierLinks } from "./venue.boards.js";
|
|
15
15
|
import { n, shy, wall } from "./turn.kit.js";
|
|
16
16
|
export const NO_VENUE = { items: [], defs: "", reach: 0 };
|
|
@@ -38,7 +38,7 @@ export function standsVenue(o, v, lift) {
|
|
|
38
38
|
const crowd = resolveCrowd(o.crowd);
|
|
39
39
|
if (stands === undefined || crowd === undefined || stands.gallery === true)
|
|
40
40
|
return NO_VENUE;
|
|
41
|
-
return standsVenueOf(stands, crowd, { sport: o.sport, detail: o.detail, ground: o.ground }, v, lift);
|
|
41
|
+
return standsVenueOf(stands, crowd, { sport: o.sport, detail: o.detail, ground: o.ground }, { ...v, team: v.team ?? teamColors(o, crowd) }, lift);
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
44
|
* The stands the model says (`crowdStands`, or the stands of a tournament layout's hall) as planes: what `standsVenue` makes of a surface's, for stands that were worked out some other way. `o` is the little of the
|
|
@@ -76,17 +76,17 @@ export function standsVenueOf(stands, crowd, o, v, lift) {
|
|
|
76
76
|
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}"/>`;
|
|
77
77
|
}
|
|
78
78
|
if (light !== undefined)
|
|
79
|
-
svg += `<polygon class="${
|
|
79
|
+
svg += `<polygon class="${light.house === "dark" ? "u-veil u-veil-down" : light.house === "half" ? "u-veil u-veil-half" : "u-veil"}" points="${pointList(corners)}"/>`;
|
|
80
80
|
// The lights down: the front of each step is edged with light, and each stair aisle is a beam from above with the rails beside it (the aisle lines the stands' dark ones are, are left out).
|
|
81
81
|
if (down && light !== undefined) {
|
|
82
82
|
if (corners.length === 4)
|
|
83
|
-
svg += edgeSvg(corners[0], corners[1], light.
|
|
83
|
+
svg += edgeSvg(corners[0], corners[1], light.beam, "u-edge");
|
|
84
84
|
if (fine) {
|
|
85
85
|
ring.aisles.forEach(([a, b], i) => {
|
|
86
86
|
const seg = clipSegment(a, b, piece);
|
|
87
87
|
if (seg === undefined || Math.hypot(seg[1][0] - seg[0][0], seg[1][1] - seg[0][1]) < 0.5)
|
|
88
88
|
return;
|
|
89
|
-
svg += beamSvg(local(seg[0]), local(seg[1]), light.
|
|
89
|
+
svg += beamSvg(local(seg[0]), local(seg[1]), light.beam, "u-beam", i % 4 === 0);
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
}
|
|
@@ -116,9 +116,9 @@ export function standsVenueOf(stands, crowd, o, v, lift) {
|
|
|
116
116
|
const links = tierLinks(stands);
|
|
117
117
|
for (const l of links)
|
|
118
118
|
chained.add(l.after);
|
|
119
|
-
const light = resolveLighting(v.lighting !== undefined ? v.lighting : crowd.lighting);
|
|
120
|
-
const night = light?.
|
|
121
|
-
const down = light?.
|
|
119
|
+
const light = resolveLighting(v.lighting !== undefined ? v.lighting : crowd.lighting, v.team ?? teamColors({}, crowd));
|
|
120
|
+
const night = light?.aisles === "dots";
|
|
121
|
+
const down = light?.aisles === "beams";
|
|
122
122
|
const boards = resolveBoards(v.boards !== undefined ? v.boards : crowd.boards, { home: crowd.home, away: crowd.away }, light !== undefined);
|
|
123
123
|
const ribbons = [];
|
|
124
124
|
/** How far along the ribbon of one level the next piece of it starts, in feet, so that its panels carry on. */
|
|
@@ -208,7 +208,7 @@ export function standsVenueOf(stands, crowd, o, v, lift) {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
// The surface is lit: a pool of light on the floor round it, under the seats.
|
|
211
|
-
if (
|
|
211
|
+
if (light?.pool === true)
|
|
212
212
|
seatPlanes.push(lightPool("night-pool", stands.box, lift + APRON_RISE + 0.03, light?.wash));
|
|
213
213
|
// A colour of light on the surface (a show's wash), over the markings, which stay visible through it.
|
|
214
214
|
if (light?.wash !== undefined)
|
package/dist/venue.lighting.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type Plane } from "./turn.kit.ts";
|
|
2
2
|
import type { LightingOptions, LightingPreset } from "./types.ts";
|
|
3
|
-
|
|
4
|
-
/** The named colours of light: `wash` and `aisles` take these by name (ours, a little saturated, for a light seen on dark). */
|
|
3
|
+
/** The named colours of light: `wash`, `aisles` and `colors` take these by name (ours, a little saturated, for a light seen on dark). */
|
|
5
4
|
export declare const LIGHT_COLORS: {
|
|
6
5
|
readonly white: "#fff6e6";
|
|
7
6
|
readonly amber: "#ffb830";
|
|
@@ -10,14 +9,57 @@ export declare const LIGHT_COLORS: {
|
|
|
10
9
|
readonly blue: "#2b7bff";
|
|
11
10
|
readonly cyan: "#27d6ff";
|
|
12
11
|
};
|
|
13
|
-
/**
|
|
12
|
+
/** How dark the house is: `half` (a light veil, the game's), `dim` (the night's) or `dark` (a show's). */
|
|
13
|
+
export type House = "half" | "dim" | "dark";
|
|
14
|
+
/**
|
|
15
|
+
* A named lighting. `house` is how dark the stands are; `aisles` how the aisles are lit (`none`, small `dots`, or a `beam` of light from above down each); `pool` whether a pool of light lies on the floor round the surface; `wash` and `beam` are the colours
|
|
16
|
+
* of the light on the surface and of the beams: a colour (a name of `LIGHT_COLORS`), or `home` and `away`, the teams' colours where the options have them (`crowd.home` and `crowd.away`, the colours of a score's teams), else `fallback`'s.
|
|
17
|
+
*/
|
|
18
|
+
export interface LightPreset {
|
|
19
|
+
house: House;
|
|
20
|
+
aisles: "none" | "dots" | "beams";
|
|
21
|
+
pool: boolean;
|
|
22
|
+
wash?: keyof typeof LIGHT_COLORS | "home" | "away";
|
|
23
|
+
beam?: keyof typeof LIGHT_COLORS | "home" | "away";
|
|
24
|
+
/** What `home` and `away` are where the options name no team colour. */
|
|
25
|
+
fallback?: {
|
|
26
|
+
home: keyof typeof LIGHT_COLORS;
|
|
27
|
+
away: keyof typeof LIGHT_COLORS;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The named lightings. `night`: the calm dim house with small lights along the aisles. `game`: white light, the house half lit, a pool of light round the surface. `lightsDown`: the house nearly black, a white beam down each aisle. `intro`: the
|
|
32
|
+
* house dark, the home team's colour washed over the surface and the visitors' (the second colour) in the beams. `concert`: warm amber beams and a purple surface. (The `celebration` of the plan, colours chasing round the bowl, is a later one.)
|
|
33
|
+
*/
|
|
34
|
+
export declare const LIGHT_PRESETS: Readonly<Record<LightingPreset, LightPreset>>;
|
|
35
|
+
export declare const LIGHTING_PRESETS: readonly LightingPreset[];
|
|
36
|
+
/** The lighting as asked, with every part's value. */
|
|
14
37
|
export interface Lighting {
|
|
15
38
|
preset: LightingPreset;
|
|
16
|
-
|
|
39
|
+
house: House;
|
|
40
|
+
aisles: "none" | "dots" | "beams";
|
|
41
|
+
pool: boolean;
|
|
42
|
+
/** The colour of the beams. */
|
|
43
|
+
beam: string;
|
|
44
|
+
/** The surface's colour of light (none for the surface as it is). */
|
|
17
45
|
wash: string | undefined;
|
|
18
46
|
}
|
|
19
|
-
/** The
|
|
20
|
-
export declare function
|
|
47
|
+
/** The teams' colours the options have: a score's teams' `color`, over which a crowd's `home` and `away` stand. */
|
|
48
|
+
export declare function teamColors(o: object, crowd?: {
|
|
49
|
+
home?: string;
|
|
50
|
+
away?: string;
|
|
51
|
+
}): {
|
|
52
|
+
home?: string;
|
|
53
|
+
away?: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* The lighting an option asks for: a preset's name, `true` for the first (night), or `{ preset, colors, aisles, wash }`, or `undefined` for none (false, nothing, an unknown name). A colour comes from, in order: `wash` or `aisles`, `colors` (the first
|
|
57
|
+
* is what the preset's `home` is, the second its `away`), the teams' colours (`team`), the preset's own.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveLighting(ask: LightingOptions | undefined | null, team?: {
|
|
60
|
+
home?: string;
|
|
61
|
+
away?: string;
|
|
62
|
+
}): Lighting | undefined;
|
|
21
63
|
/** How far the pool of light round the surface reaches beyond its box, in feet, and the share of the pool that is bright (the surface's own edge). The surface itself stays as drawn: the pool is brightest just outside its markings. */
|
|
22
64
|
export declare const POOL: {
|
|
23
65
|
readonly reach: 20;
|
package/dist/venue.lighting.js
CHANGED
|
@@ -4,19 +4,43 @@
|
|
|
4
4
|
// Pure: data in, planes out.
|
|
5
5
|
import { flat, n } from "./turn.kit.js";
|
|
6
6
|
import { safeColor } from "./venue.boards.js";
|
|
7
|
-
|
|
8
|
-
/** The named colours of light: `wash` and `aisles` take these by name (ours, a little saturated, for a light seen on dark). */
|
|
7
|
+
/** The named colours of light: `wash`, `aisles` and `colors` take these by name (ours, a little saturated, for a light seen on dark). */
|
|
9
8
|
export const LIGHT_COLORS = { white: "#fff6e6", amber: "#ffb830", purple: "#9b4dff", magenta: "#ff2bd6", blue: "#2b7bff", cyan: "#27d6ff" };
|
|
9
|
+
/**
|
|
10
|
+
* The named lightings. `night`: the calm dim house with small lights along the aisles. `game`: white light, the house half lit, a pool of light round the surface. `lightsDown`: the house nearly black, a white beam down each aisle. `intro`: the
|
|
11
|
+
* house dark, the home team's colour washed over the surface and the visitors' (the second colour) in the beams. `concert`: warm amber beams and a purple surface. (The `celebration` of the plan, colours chasing round the bowl, is a later one.)
|
|
12
|
+
*/
|
|
13
|
+
export const LIGHT_PRESETS = {
|
|
14
|
+
night: { house: "dim", aisles: "dots", pool: true },
|
|
15
|
+
game: { house: "half", aisles: "none", pool: true },
|
|
16
|
+
lightsDown: { house: "dark", aisles: "beams", pool: false },
|
|
17
|
+
intro: { house: "dark", aisles: "beams", pool: false, wash: "home", beam: "away", fallback: { home: "blue", away: "amber" } },
|
|
18
|
+
concert: { house: "dark", aisles: "beams", pool: false, wash: "purple", beam: "amber" },
|
|
19
|
+
};
|
|
20
|
+
export const LIGHTING_PRESETS = Object.keys(LIGHT_PRESETS);
|
|
10
21
|
const colorOf = (value) => (typeof value === "string" && Object.hasOwn(LIGHT_COLORS, value) ? LIGHT_COLORS[value] : safeColor(value));
|
|
11
|
-
/** The
|
|
12
|
-
export function
|
|
22
|
+
/** The teams' colours the options have: a score's teams' `color`, over which a crowd's `home` and `away` stand. */
|
|
23
|
+
export function teamColors(o, crowd) {
|
|
24
|
+
const teams = o;
|
|
25
|
+
return { home: safeColor(crowd?.home) ?? safeColor(teams.home?.color), away: safeColor(crowd?.away) ?? safeColor(teams.away?.color) };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The lighting an option asks for: a preset's name, `true` for the first (night), or `{ preset, colors, aisles, wash }`, or `undefined` for none (false, nothing, an unknown name). A colour comes from, in order: `wash` or `aisles`, `colors` (the first
|
|
29
|
+
* is what the preset's `home` is, the second its `away`), the teams' colours (`team`), the preset's own.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveLighting(ask, team = {}) {
|
|
13
32
|
if (ask === undefined || ask === null || ask === false)
|
|
14
33
|
return undefined;
|
|
15
34
|
const o = typeof ask === "object" ? ask : {};
|
|
16
35
|
const name = typeof ask === "string" ? ask : ask === true ? "night" : o.preset ?? "night";
|
|
17
36
|
if (!LIGHTING_PRESETS.includes(name))
|
|
18
37
|
return undefined;
|
|
19
|
-
|
|
38
|
+
const preset = name;
|
|
39
|
+
const base = LIGHT_PRESETS[preset];
|
|
40
|
+
const given = (Array.isArray(o.colors) ? o.colors : []).map(colorOf).filter((c) => c !== undefined);
|
|
41
|
+
const side = (which) => (which === "home" ? given[0] ?? team.home : given[1] ?? team.away) ?? LIGHT_COLORS[base.fallback?.[which] ?? (which === "home" ? "blue" : "amber")];
|
|
42
|
+
const pick = (token) => (token === undefined ? undefined : token === "home" || token === "away" ? side(token) : LIGHT_COLORS[token]);
|
|
43
|
+
return { preset, house: base.house, aisles: base.aisles, pool: base.pool, beam: colorOf(o.aisles) ?? pick(base.beam) ?? LIGHT_COLORS.white, wash: colorOf(o.wash) ?? pick(base.wash) };
|
|
20
44
|
}
|
|
21
45
|
/** How far the pool of light round the surface reaches beyond its box, in feet, and the share of the pool that is bright (the surface's own edge). The surface itself stays as drawn: the pool is brightest just outside its markings. */
|
|
22
46
|
export const POOL = { reach: 20, edge: 0.7, strength: 0.34 };
|
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.
|
|
2
|
+
export declare const VERSION = "0.40.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.
|
|
2
|
+
export const VERSION = "0.40.1";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/kyogijo-stadiums",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.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",
|