@johnmorrisdotca/kyogijo-stadiums 0.34.1 → 0.36.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 CHANGED
@@ -7,6 +7,24 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.36.0] - 2026-10-20
11
+
12
+ ### Added
13
+
14
+ - **A diamond mowing pattern** (`stripePattern: "diamonds"`, `菱形` in the demo): the crosshatch of a ballpark's outfield, as in a photograph of T-Mobile Park. On a ballpark it is squares of 30 feet with their sides along the foul lines, from home plate out, lighter and darker alternately (two passes of 45-degree stripes crossing); on a football field and a soccer pitch it is squares of a band turned 45 degrees, one of them centred on the turf so that both ends are mown alike, whatever the size. It is cut to the fair ground or to the turf by a clip path. The infield grass stays one tone, and a separate infield pattern is not offered: the pattern is the outfield's. [Theming](./docs/theming.md), the gallery's mowing sections.
15
+ - **The demo's Mowing choice is for every grass sport**: a football field and a ballpark have it too (it was the soccer pitch's alone), with Diamonds among the choices; the soccer section of the home page shows the diamonds as one more card.
16
+
17
+ ## [0.35.0] - 2026-10-20
18
+
19
+ ### Added
20
+
21
+ - **The auto-turn goes either way** (`TurnSettings.direction`, `KyogijoTurn.direction()` and `setDirection()`, `turnDirection()`, `TurnSettings.onDirectionChange`, the element's `turn-direction`): `"counterclockwise"` is how it has always turned, the near side swinging to the viewer's right, and stays the default; `"clockwise"` is the other way. The 3D view has a **Reverse** button (`逆回転`), a toggle pressed while it goes clockwise, that changes the way at once without stopping the turn and says so aloud; a drag, the keys and the other buttons are not reversed. Every 3D view has it: the stadiums, the districts, the tournament layouts and the judging view too. [The 3D view](./docs/turn.md#the-auto-turn).
22
+
23
+ ### Changed
24
+
25
+ - **The demo waits still and turns on the pointer** (`demo/turn-way.js`): every 3D page of the demo (the home page and its featured arena, the venues, stadiums, districts, tournament layouts, judging and boards pages) opens still, turns while the pointer is over the view, and holds when it leaves; a tap on a phone starts it and a second tap holds it; Auto-turn still starts it for good (`spin=on` on the venues page's address), and a reader who prefers reduced motion is never turned for by the pointer. The pages turn at half the old speed (6 degrees a second; the package's own default, 12, is unchanged), and Reverse is kept in the address as `dir=cw`. The replays play while the pointer is over them in the same way. The fly-through page opens still and flies on the pointer at `speed=0.5`, half the pace (`?play=1` has it fly from the start).
26
+ - **The README opens on the still pictures again**: the two turning pictures at the head are gone, and each moving picture is beside its own section (the real venues, the tournament layouts, the fly-through), at half the speed (100 milliseconds a frame for the arenas, 260 for the fly-through); the real venues' pictures are taken again with the filled bowls of 0.34.1. The pictures only a page of docs shows are in `docs/pictures`, so that `docs/images` holds the README's own: the family's README lint (`scripts/readme-lint.mjs`) is again its own file with one rule added, the moving pictures' budget.
27
+
10
28
  ## [0.34.1] - 2026-10-20
11
29
 
12
30
  ### Fixed
package/README.md CHANGED
@@ -13,25 +13,6 @@ The surfaces of forty-three sports, from football and curling to the budō and t
13
13
 
14
14
  <p align="center"><a href="https://johnmorrisdotca.github.io/kyogijo-stadiums/"><strong>Open the demo →</strong></a> · <a href="https://johnmorrisdotca.github.io/kyogijo-stadiums/docs/">Documentation</a> · <a href="https://johnmorrisdotca.github.io/kyogijo-stadiums/docs/gallery.html">Gallery</a> · <a href="./docs/compare.md">How it compares</a> · <a href="https://johnmorrisdotca.github.io/kyogijo-stadiums/api.html">API reference</a></p>
15
15
 
16
- <table align="center">
17
- <tr>
18
- <td align="center" valign="top">
19
- <picture>
20
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/turning-venue-desk-dark.webp">
21
- <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/turning-venue-desk-light.webp" alt="A real arena turning once round in 3D: Climate Pledge Arena in the outline of its building, two tiers of seats round the rink." width="410">
22
- </picture>
23
- <br><em>A real arena, turning: Climate Pledge Arena in the outline of its building (OpenStreetMap).</em>
24
- </td>
25
- <td align="center" valign="top">
26
- <picture>
27
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/turning-hall-desk-dark.webp">
28
- <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/turning-hall-desk-light.webp" alt="A tournament layout turning once round in 3D: four table tennis areas on one floor, a surround round each and a scoreboard hung over them." width="410">
29
- </picture>
30
- <br><em>A tournament layout, turning: four table tennis areas under one scoreboard.</em>
31
- </td>
32
- </tr>
33
- </table>
34
-
35
16
  > **Renamed.** Previously `@johnmorrisdotca/kyogijo`; renamed in 0.4.0 to `@johnmorrisdotca/kyogijo-stadiums`. The product is still Kyogijo Stadiums (競技場); only the package and the repository names changed. See the [changelog](./CHANGELOG.md).
36
17
 
37
18
  <table align="center">
@@ -409,6 +390,7 @@ import { mountKyogijoTurn } from "@johnmorrisdotca/kyogijo-stadiums/turn";
409
390
 
410
391
  const view = mountKyogijoTurn(document.querySelector("#court")!, { sport: "volleyball", status: "live" }, { preset: "end" });
411
392
  view.preset("corner");
393
+ view.setDirection("clockwise"); // the auto-turn goes the other way (`direction` in the settings says it at the start)
412
394
  ```
413
395
 
414
396
  ```ts
@@ -420,7 +402,7 @@ console.log(items.map((item) => item.kind).join(), html.includes("preserve-3d"))
420
402
  // net,netposts,antennae false
421
403
  ```
422
404
 
423
- The flat drawing is the floor, turned by a drag, a swipe, the keys or four viewpoints, with what the rulebook stands on it at its height; each part is one `matrix3d`. In the element: `<kyogijo-field sport="nhl" turn turn-view="broadcast">`. [The 3D view](./docs/turn.md).
405
+ The flat drawing is the floor, turned by a drag, a swipe, the keys or four viewpoints, with what the rulebook stands on it at its height; each part is one `matrix3d`. The auto-turn waits for Play, and turns either way: `counterclockwise` (the near side swinging to the viewer's right) by default, `clockwise` on the Reverse button. In the element: `<kyogijo-field sport="nhl" turn turn-view="broadcast">`. [The 3D view](./docs/turn.md).
424
406
 
425
407
  <p align="center">
426
408
  <picture>
@@ -561,6 +543,14 @@ console.log(layout.needs.length, layout.needs.width);
561
543
  // 47 19
562
544
  ```
563
545
 
546
+ <p align="center">
547
+ <picture>
548
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/turning-hall-desk-dark.webp">
549
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/turning-hall-desk-light.webp" alt="A tournament layout turning once round in 3D, slowly: four table tennis areas on one floor, a surround round each and a scoreboard hung over them." width="640">
550
+ </picture>
551
+ <br><em>A tournament layout, turning: four table tennis areas under one scoreboard.</em>
552
+ </p>
553
+
564
554
  ### Boards over a surface
565
555
 
566
556
  `board` lays a results board, a bout presentation or a medal table, any SVG, over a surface in 2D and 3D, off its middle and never stretched ([boards](./docs/boards.md)).
@@ -591,7 +581,7 @@ console.log(layout.needs.length, layout.needs.width);
591
581
 
592
582
  ### The real venues
593
583
 
594
- `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)). In 3D the demo's [venues page](https://johnmorrisdotca.github.io/kyogijo-stadiums/venues.html) turns by itself, with the stands, walls, roof, building, markings and labels switched on the view while it turns.
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)). 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, building, markings and labels switched on the view while it turns.
595
585
 
596
586
  ```ts
597
587
  import { drawKyogijo } from "@johnmorrisdotca/kyogijo-stadiums";
@@ -602,6 +592,14 @@ console.log(svg.includes("ky-cr-shell"), planVenue(venueById("climate-pledge-are
602
592
  // true 17151
603
593
  ```
604
594
 
595
+ <p align="center">
596
+ <picture>
597
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/turning-venue-desk-dark.webp">
598
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/turning-venue-desk-light.webp" alt="A real arena turning once round in 3D, slowly: Climate Pledge Arena in the outline of its building, two tiers of seats with a suite level of windows between them, and a darker concourse floor round the bowl." width="640">
599
+ </picture>
600
+ <br><em>A real arena, turning: Climate Pledge Arena in the outline of its building (OpenStreetMap).</em>
601
+ </p>
602
+
605
603
  ### A real stadium, and a district
606
604
 
607
605
  `stadium: "bc-place"` draws BC Place for football or soccer under its cable roof, and Lumen Field, T-Mobile Park, Tokyo Dome, Koshien Stadium (`park`) and Korakuen Hall (`sport: "boxing"`) likewise ([stadiums](./docs/stadiums.md)); `drawDistrict({ venues: ["bc-place", "rogers-arena"] })` puts venues at their real positions, tight as neighbours or spread as a city, to turn together in 3D ([districts](./docs/district.md)).
package/dist/draw.js CHANGED
@@ -222,7 +222,7 @@ const safeLength = (value) => {
222
222
  return Number.isFinite(value) && value > 0 ? num(value) : undefined;
223
223
  return typeof value === "string" && /^\d+(?:\.\d+)?(?:px|%|em|rem|vw|vh)?$/.test(value.trim()) ? value.trim() : undefined;
224
224
  };
225
- const PATTERNS = ["bands", "wide", "narrow", "lengthwise", "checks", "diagonal", "circles", "rings", "fan", "none"];
225
+ const PATTERNS = ["bands", "wide", "narrow", "lengthwise", "checks", "diagonal", "diamonds", "circles", "rings", "fan", "none"];
226
226
  const GOALS = ["box", "line", "strip", "none"];
227
227
  /** The pitch the options ask for: a `pitch` of the caller's, else the size a `ground` publishes, else none (the international pitch). */
228
228
  export function pitchOf(o) {
package/dist/element.d.ts CHANGED
@@ -6,7 +6,7 @@ import type { ScoreOptions, SurfaceOptions } from "./types.ts";
6
6
  /** The tag of the element. */
7
7
  export declare const KYOGIJO_TAG = "kyogijo-field";
8
8
  /** The attributes the element reads. */
9
- export declare const KYOGIJO_ATTRIBUTES: readonly ["sport", "league", "size", "theme", "language", "orientation", "park", "ground", "arena", "year", "detail", "margin", "stripes", "exact-lines", "uppercase", "pitch", "pitch-type", "half", "range", "range-end", "goals", "stripe-pattern", "corner-flags", "look", "line-width", "line-dash", "line-opacity", "landmarks", "walls", "quirks", "home", "away", "home-abbr", "away-abbr", "home-score", "away-score", "home-penalties", "away-penalties", "score", "home-logo", "away-logo", "home-flag", "away-flag", "home-color", "away-color", "status", "competition", "date", "label", "winner", "home-side", "pageant", "description", "options", "loop", "speed", "autoplay", "controls", "trail", "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch", "turn-venue", "turn-roof", "turn-autoplay", "turn-speed"];
9
+ export declare const KYOGIJO_ATTRIBUTES: readonly ["sport", "league", "size", "theme", "language", "orientation", "park", "ground", "arena", "year", "detail", "margin", "stripes", "exact-lines", "uppercase", "pitch", "pitch-type", "half", "range", "range-end", "goals", "stripe-pattern", "corner-flags", "look", "line-width", "line-dash", "line-opacity", "landmarks", "walls", "quirks", "home", "away", "home-abbr", "away-abbr", "home-score", "away-score", "home-penalties", "away-penalties", "score", "home-logo", "away-logo", "home-flag", "away-flag", "home-color", "away-color", "status", "competition", "date", "label", "winner", "home-side", "pageant", "description", "options", "loop", "speed", "autoplay", "controls", "trail", "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch", "turn-venue", "turn-roof", "turn-autoplay", "turn-speed", "turn-direction"];
10
10
  /** The settings of a 3D view an element's attributes make: `turn-view` names the viewpoint it opens on, `turn-controls="false"` and `turn-uprights="false"` leave the buttons and what stands on the surface out, `turn-score="floor"` leaves the score panel on the floor, `turn-touch="turn"` keeps every touch for the view, `turn-venue="false"` leaves out the stands and the room (they are raised for a drawing with a crowd and for a dojo), `turn-roof` puts a roof or canopy over the stands, `turn-autoplay` starts the auto-turn (`force` even for a reader who prefers reduced motion) and `turn-speed` is its degrees a second. */
11
11
  export declare function turnSettingsFromAttributes(read: (name: string) => string | null, base?: TurnSettings): TurnSettings;
12
12
  /** Whether an element's `turn` attribute asks for the 3D view: present, and not `false`. */
@@ -24,7 +24,7 @@ export declare function optionsFromAttributes(read: (name: string) => string | n
24
24
  declare const Base: typeof HTMLElement;
25
25
  export declare class KyogijoField extends Base {
26
26
  #private;
27
- static observedAttributes: readonly ["sport", "league", "size", "theme", "language", "orientation", "park", "ground", "arena", "year", "detail", "margin", "stripes", "exact-lines", "uppercase", "pitch", "pitch-type", "half", "range", "range-end", "goals", "stripe-pattern", "corner-flags", "look", "line-width", "line-dash", "line-opacity", "landmarks", "walls", "quirks", "home", "away", "home-abbr", "away-abbr", "home-score", "away-score", "home-penalties", "away-penalties", "score", "home-logo", "away-logo", "home-flag", "away-flag", "home-color", "away-color", "status", "competition", "date", "label", "winner", "home-side", "pageant", "description", "options", "loop", "speed", "autoplay", "controls", "trail", "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch", "turn-venue", "turn-roof", "turn-autoplay", "turn-speed"];
27
+ static observedAttributes: readonly ["sport", "league", "size", "theme", "language", "orientation", "park", "ground", "arena", "year", "detail", "margin", "stripes", "exact-lines", "uppercase", "pitch", "pitch-type", "half", "range", "range-end", "goals", "stripe-pattern", "corner-flags", "look", "line-width", "line-dash", "line-opacity", "landmarks", "walls", "quirks", "home", "away", "home-abbr", "away-abbr", "home-score", "away-score", "home-penalties", "away-penalties", "score", "home-logo", "away-logo", "home-flag", "away-flag", "home-color", "away-color", "status", "competition", "date", "label", "winner", "home-side", "pageant", "description", "options", "loop", "speed", "autoplay", "controls", "trail", "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch", "turn-venue", "turn-roof", "turn-autoplay", "turn-speed", "turn-direction"];
28
28
  /** The options given as a property: an object, laid under the attributes. */
29
29
  get options(): Partial<ScoreOptions> | undefined;
30
30
  set options(value: Partial<ScoreOptions> | undefined);
package/dist/element.js CHANGED
@@ -25,7 +25,7 @@ export const KYOGIJO_ATTRIBUTES = [
25
25
  "home", "away", "home-abbr", "away-abbr", "home-score", "away-score", "home-penalties", "away-penalties", "score", "home-logo", "away-logo", "home-flag", "away-flag", "home-color", "away-color",
26
26
  "status", "competition", "date", "label", "winner", "home-side", "pageant", "description", "options",
27
27
  "loop", "speed", "autoplay", "controls", "trail",
28
- "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch", "turn-venue", "turn-roof", "turn-autoplay", "turn-speed",
28
+ "turn", "turn-view", "turn-controls", "turn-uprights", "turn-score", "turn-touch", "turn-venue", "turn-roof", "turn-autoplay", "turn-speed", "turn-direction",
29
29
  ];
30
30
  /** The attributes that are about a replay and not about the drawing. */
31
31
  const REPLAY_ATTRIBUTES = ["loop", "speed", "autoplay", "controls", "trail"];
@@ -57,6 +57,9 @@ export function turnSettingsFromAttributes(read, base = {}) {
57
57
  const speed = Number(read("turn-speed"));
58
58
  if (read("turn-speed") !== null && Number.isFinite(speed))
59
59
  out.speed = speed;
60
+ const direction = read("turn-direction");
61
+ if (direction === "clockwise" || direction === "counterclockwise")
62
+ out.direction = direction;
60
63
  return out;
61
64
  }
62
65
  /** Whether an element's `turn` attribute asks for the 3D view: present, and not `false`. */
@@ -59,6 +59,8 @@ export function mountJudgingBroadcast(host, options, settings = {}) {
59
59
  },
60
60
  play: () => turn.play(),
61
61
  pause: () => turn.pause(),
62
+ direction: () => turn.direction(),
63
+ setDirection: (direction) => turn.setDirection(direction),
62
64
  togglePlay: () => turn.togglePlay(),
63
65
  svg: () => turn.svg(),
64
66
  destroy: () => {
package/dist/mow.d.ts CHANGED
@@ -18,7 +18,8 @@ export declare const oddCells: (length: number, cell: number) => number;
18
18
  /**
19
19
  * The lighter tone of a pattern on a rectangle of turf. `band` is the width of the usual stripe, along x; `wide` and `narrow` are twice and half of it.
20
20
  * `bands` run across the turf (side by side along x), `lengthwise` along it, `checks` are squares of about `band`, `diagonal` are 45-degree stripes
21
- * `band` wide across, and `circles` are rings `band` wide about the middle. A pattern that is not one of these (`none`, `rings`, `fan`) has no tone here.
21
+ * `band` wide across, `diamonds` are the squares of `checks` turned 45 degrees (the crosshatch of a ballpark's outfield: two passes of 45-degree stripes, `band` wide
22
+ * across, crossing), laid so that a diamond is centred on the turf and both ends are mown alike, and `circles` are rings `band` wide about the middle. A pattern that is not one of these (`none`, `rings`, `fan`) has no tone here.
22
23
  */
23
24
  export declare function mowTurf(pattern: StripePattern, turf: Turf, band: number): Mown;
24
25
  /** A ballpark's fair ground, as the field of its stripes: how far it reaches either side of the line to centre field and how far out. */
@@ -30,7 +31,8 @@ export interface Park {
30
31
  /**
31
32
  * The lighter tone of a pattern on a ballpark: coordinates are the field's screen frame (x across, y minus the distance out from home plate). `band` is
32
33
  * 30 feet, the width of a stripe of the usual rings. `bands` run across the field at fixed distances out from home, `lengthwise` along the line to centre
33
- * field, `checks` are squares, `diagonal` run at 45 degrees, `fan` is wedges out from home plate, 6 degrees each. `rings` and `circles` are the park's own
34
+ * field, `checks` are squares, `diagonal` run at 45 degrees, `diamonds` are squares of `band` with their sides along the foul lines (two passes of stripes parallel to the
35
+ * lines, crossing, from home plate out), `fan` is wedges out from home plate, 6 degrees each. `rings` and `circles` are the park's own
34
36
  * arcs about home plate (drawn by the field itself) and `none` is plain, so they have no tone here. The path runs past the fence and must be cut to the fair ground.
35
37
  */
36
38
  export declare function mowPark(pattern: StripePattern, park: Park, band?: number): Mown;
package/dist/mow.js CHANGED
@@ -5,6 +5,10 @@ import { num } from "./format.js";
5
5
  const NONE = { path: "", clip: false, evenodd: false };
6
6
  /** An odd whole number of cells nearest to a length over a cell: odd, so that both ends of the turf are mown alike. */
7
7
  export const oddCells = (length, cell) => Math.max(3, 2 * Math.round((length / cell - 1) / 2) + 1);
8
+ /** A convex polygon from its corners, as a closed subpath. */
9
+ const poly = (corners) => `M${corners.map(([px, py]) => `${num(px)} ${num(py)}`).join("L")}z`;
10
+ /** The corners of the square of a lattice turned a quarter of a right angle: the cell between `a0` and `a1` along the first axis `(ex, ey)` and `b0` and `b1` along the second, from the origin `(ox, oy)`. */
11
+ const lattice = (ox, oy, ex, ey, fx, fy, a0, a1, b0, b1) => [[a0, b0], [a1, b0], [a1, b1], [a0, b1]].map(([a, b]) => [ox + a * ex + b * fx, oy + a * ey + b * fy]);
8
12
  const box = (x, y, w, h) => `M${num(x)} ${num(y)}h${num(w)}v${num(h)}h${num(-w)}z`;
9
13
  /** An annulus between two radii about a centre, as two circles of one path. */
10
14
  const ring = (cx, cy, inner, outer) => {
@@ -14,7 +18,8 @@ const ring = (cx, cy, inner, outer) => {
14
18
  /**
15
19
  * The lighter tone of a pattern on a rectangle of turf. `band` is the width of the usual stripe, along x; `wide` and `narrow` are twice and half of it.
16
20
  * `bands` run across the turf (side by side along x), `lengthwise` along it, `checks` are squares of about `band`, `diagonal` are 45-degree stripes
17
- * `band` wide across, and `circles` are rings `band` wide about the middle. A pattern that is not one of these (`none`, `rings`, `fan`) has no tone here.
21
+ * `band` wide across, `diamonds` are the squares of `checks` turned 45 degrees (the crosshatch of a ballpark's outfield: two passes of 45-degree stripes, `band` wide
22
+ * across, crossing), laid so that a diamond is centred on the turf and both ends are mown alike, and `circles` are rings `band` wide about the middle. A pattern that is not one of these (`none`, `rings`, `fan`) has no tone here.
18
23
  */
19
24
  export function mowTurf(pattern, turf, band) {
20
25
  const { x, y, w, h } = turf;
@@ -49,6 +54,26 @@ export function mowTurf(pattern, turf, band) {
49
54
  parts.push(`M${num(a)} ${num(y + h)}L${num(a + h)} ${num(y)}L${num(a + h + across)} ${num(y)}L${num(a + across)} ${num(y + h)}z`);
50
55
  return { path: parts.join(""), clip: true, evenodd: false };
51
56
  }
57
+ case "diamonds": {
58
+ // A lattice of squares of side `band` turned 45 degrees, one square centred on the turf: both ends of the turf then have the same tone, whatever its size.
59
+ const cx = x + w / 2;
60
+ const cy = y + h / 2;
61
+ const r = Math.SQRT1_2;
62
+ const reach = Math.ceil((Math.hypot(w, h) / 2) / band) + 1;
63
+ for (let i = -reach; i <= reach; i += 1) {
64
+ for (let j = -reach; j <= reach; j += 1) {
65
+ if (Math.abs(i + j) % 2 !== 1)
66
+ continue;
67
+ const corners = lattice(cx, cy, r, r, -r, r, (i - 0.5) * band, (i + 0.5) * band, (j - 0.5) * band, (j + 0.5) * band);
68
+ const xs = corners.map((c) => c[0]);
69
+ const ys = corners.map((c) => c[1]);
70
+ if (Math.max(...xs) <= x || Math.min(...xs) >= x + w || Math.max(...ys) <= y || Math.min(...ys) >= y + h)
71
+ continue;
72
+ parts.push(poly(corners));
73
+ }
74
+ }
75
+ return { path: parts.join(""), clip: true, evenodd: false };
76
+ }
52
77
  case "circles": {
53
78
  const cx = x + w / 2;
54
79
  const cy = y + h / 2;
@@ -64,7 +89,8 @@ export function mowTurf(pattern, turf, band) {
64
89
  /**
65
90
  * The lighter tone of a pattern on a ballpark: coordinates are the field's screen frame (x across, y minus the distance out from home plate). `band` is
66
91
  * 30 feet, the width of a stripe of the usual rings. `bands` run across the field at fixed distances out from home, `lengthwise` along the line to centre
67
- * field, `checks` are squares, `diagonal` run at 45 degrees, `fan` is wedges out from home plate, 6 degrees each. `rings` and `circles` are the park's own
92
+ * field, `checks` are squares, `diagonal` run at 45 degrees, `diamonds` are squares of `band` with their sides along the foul lines (two passes of stripes parallel to the
93
+ * lines, crossing, from home plate out), `fan` is wedges out from home plate, 6 degrees each. `rings` and `circles` are the park's own
68
94
  * arcs about home plate (drawn by the field itself) and `none` is plain, so they have no tone here. The path runs past the fence and must be cut to the fair ground.
69
95
  */
70
96
  export function mowPark(pattern, park, band = 30) {
@@ -96,6 +122,24 @@ export function mowPark(pattern, park, band = 30) {
96
122
  parts.push(`M${num(a)} 0L${num(a + reach)} ${num(-reach)}L${num(a + reach + across)} ${num(-reach)}L${num(a + across)} 0z`);
97
123
  return { path: parts.join(""), clip: true, evenodd: false };
98
124
  }
125
+ case "diamonds": {
126
+ // The foul lines are the axes: a cell is `band` along each, from home plate, and the cells either side of a corner alternate.
127
+ const r = Math.SQRT1_2;
128
+ const last = Math.ceil((reach * Math.SQRT2) / band) + 1;
129
+ for (let i = 0; i < last; i += 1) {
130
+ for (let j = 0; j < last; j += 1) {
131
+ if ((i + j) % 2 !== 1)
132
+ continue;
133
+ const corners = lattice(0, 0, r, -r, -r, -r, i * band, (i + 1) * band, j * band, (j + 1) * band);
134
+ const xs = corners.map((c) => c[0]);
135
+ const ys = corners.map((c) => c[1]);
136
+ if (Math.max(...xs) < umin - band || Math.min(...xs) > umax + band || Math.min(...ys) > band || Math.max(...ys) < -reach)
137
+ continue;
138
+ parts.push(poly(corners));
139
+ }
140
+ }
141
+ return { path: parts.join(""), clip: true, evenodd: false };
142
+ }
99
143
  case "fan": {
100
144
  const step = (6 * Math.PI) / 180;
101
145
  const R = reach * 1.5;
package/dist/strings.d.ts CHANGED
@@ -1030,6 +1030,11 @@ export interface KyogijoWords {
1030
1030
  playOff: string;
1031
1031
  announcePlay: string;
1032
1032
  announcePause: string;
1033
+ /** The Reverse button's name (pressed while the auto-turn goes clockwise), what it is for, and what is announced when the direction changes. */
1034
+ reverse: string;
1035
+ reverseLabel: string;
1036
+ announceClockwise: string;
1037
+ announceCounterclockwise: string;
1033
1038
  /** What is announced after a key or a button: {name} for a viewpoint, and the angles: {yaw} and {pitch} in degrees. */
1034
1039
  announce: string;
1035
1040
  announceAngle: string;
package/dist/strings.js CHANGED
@@ -451,7 +451,7 @@ export const KYOGIJO_STRINGS = {
451
451
  keys: "A 3D view you can turn. Drag, or use the arrow keys, to turn and tilt it; plus and minus zoom; 1 to 4 pick a viewpoint; 0 or Home puts it back; Space starts and stops the auto-turn. Pinch, or hold Ctrl and scroll, to zoom.",
452
452
  overhead: "Overhead", broadcast: "Broadcast", end: "End-on", corner: "Low corner",
453
453
  reset: "Reset the view", turnLeft: "Turn left", turnRight: "Turn right", raise: "Tip the surface away", lower: "Tip the surface toward you", zoomIn: "Zoom in", zoomOut: "Zoom out",
454
- play: "Auto-turn", playOn: "Start the auto-turn", playOff: "Pause the auto-turn", announcePlay: "Auto-turn on", announcePause: "Auto-turn paused",
454
+ play: "Auto-turn", playOn: "Start the auto-turn", playOff: "Pause the auto-turn", announcePlay: "Auto-turn on", announcePause: "Auto-turn paused", reverse: "Reverse", reverseLabel: "Turn the other way", announceClockwise: "Turning clockwise", announceCounterclockwise: "Turning counterclockwise",
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",
@@ -1010,7 +1010,7 @@ export const KYOGIJO_STRINGS = {
1010
1010
  keys: "回して見られる3D表示です。ドラッグするか矢印キーで回転と傾きを変え、プラスとマイナスのキーで拡大と縮小、1から4のキーで視点を選び、0キーまたはHomeキーで元に戻し、スペースキーで自動回転を始めたり止めたりします。ピンチ操作、またはCtrlキーを押しながらのスクロールでも拡大・縮小できます。",
1011
1011
  overhead: "真上", broadcast: "中継カメラ", end: "エンド側から", corner: "低い角度から",
1012
1012
  reset: "元に戻す", turnLeft: "左へ回す", turnRight: "右へ回す", raise: "床面を奥へ傾ける", lower: "床面を手前へ傾ける", zoomIn: "拡大", zoomOut: "縮小",
1013
- play: "自動回転", playOn: "自動回転を始める", playOff: "自動回転を一時停止する", announcePlay: "自動回転を始めました", announcePause: "自動回転を一時停止しました",
1013
+ play: "自動回転", playOn: "自動回転を始める", playOff: "自動回転を一時停止する", announcePlay: "自動回転を始めました", announcePause: "自動回転を一時停止しました", reverse: "逆回転", reverseLabel: "逆向きに回す", announceClockwise: "時計回りに回転しています", announceCounterclockwise: "反時計回りに回転しています",
1014
1014
  announce: "{name}の視点", announceAngle: "向きは{yaw}度、見下ろす角度は{pitch}度です。",
1015
1015
  flat: "このブラウザーでは3D表示ができないため、平面の図を表示しています。",
1016
1016
  standing: "3D表示で立って見えるもの:{list}。", item: "{name} {h}", itemSag: "{name} 支柱の位置で{h}(中央は{low})",
@@ -13,10 +13,10 @@ const BANDS = { bands: 19, wide: 9, narrow: 39 };
13
13
  function stripes(pattern, L, W, id) {
14
14
  if (pattern === "none")
15
15
  return { path: "", defs: "" };
16
- if (pattern === "circles") {
17
- // Rings about the middle, as wide as a usual band, cut to the pitch.
18
- const mown = mowTurf("circles", { x: 0, y: 0, w: L, h: W }, L / BANDS.bands);
19
- return { path: mown.path, defs: `<clipPath id="${id}-turf"><rect x="0" y="0" width="${n(L)}" height="${n(W)}"/></clipPath>`, evenodd: true };
16
+ if (pattern === "circles" || pattern === "diamonds") {
17
+ // Rings about the middle, or squares turned 45 degrees about it, as wide as a usual band, cut to the pitch.
18
+ const mown = mowTurf(pattern, { x: 0, y: 0, w: L, h: W }, L / BANDS.bands);
19
+ return { path: mown.path, defs: `<clipPath id="${id}-turf"><rect x="0" y="0" width="${n(L)}" height="${n(W)}"/></clipPath>`, evenodd: mown.evenodd };
20
20
  }
21
21
  const rect = (x, y, w, h) => `M${n(x)} ${n(y)}h${n(w)}v${n(h)}h${n(-w)}z`;
22
22
  const parts = [];
@@ -1,6 +1,8 @@
1
1
  export { mountKyogijoTurn, mountTurn, turnSupported } from "./turn.dom.ts";
2
2
  export type { KyogijoTurn } from "./turn.dom.ts";
3
3
  export { buildTurn, floorOptions, openingView } from "./turn.model.ts";
4
+ export { turnDirection } from "./turn.camera.ts";
5
+ export type { TurnDirection } from "./turn.camera.ts";
4
6
  export type { TurnModel, TurnSettings } from "./turn.model.ts";
5
7
  export { layoutOf, planeMarkup, planeMatrix, turnMarkup, turnMarkupOf } from "./turn.markup.ts";
6
8
  export type { Layout } from "./turn.markup.ts";
@@ -1,6 +1,7 @@
1
1
  // The 3D view: a surface as a floor that turns, with a net, a basket, a goal or a pair of posts standing on it.
2
2
  export { mountKyogijoTurn, mountTurn, turnSupported } from "./turn.dom.js";
3
3
  export { buildTurn, floorOptions, openingView } from "./turn.model.js";
4
+ export { turnDirection } from "./turn.camera.js";
4
5
  export { layoutOf, planeMarkup, planeMatrix, turnMarkup, turnMarkupOf } from "./turn.markup.js";
5
6
  export { KYOGIJO_TURN_STYLE } from "./turn.style.js";
6
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";
@@ -7,6 +7,13 @@ export interface TurnView {
7
7
  /** The named viewpoints. */
8
8
  export type TurnPreset = "overhead" | "broadcast" | "end" | "corner";
9
9
  /** The viewpoints, in the order the controls and the number keys list them. */
10
+ /**
11
+ * Which way the auto-turn goes, seen from above. `"counterclockwise"` is how it has always turned: the near side swings to the viewer's right. `"clockwise"` is the other way: the near side swings to the left.
12
+ * A drag, the keys and the buttons are not reversed.
13
+ */
14
+ export type TurnDirection = "clockwise" | "counterclockwise";
15
+ /** A direction from anything: `"clockwise"` is clockwise, and everything else is the default, `"counterclockwise"`. */
16
+ export declare function turnDirection(value: unknown): TurnDirection;
10
17
  export declare const TURN_PRESET_NAMES: readonly TurnPreset[];
11
18
  /**
12
19
  * The four viewpoints. `overhead` looks straight down; `broadcast` is the main camera, high on one side line and turned a little towards the near end so that the boards, the nets and the goals show their faces; `end` stands behind an end line, along the
@@ -3,7 +3,10 @@
3
3
  // worked out in Node, tested, and handed to a stylesheet as one `transform`.
4
4
  //
5
5
  // CSS has x to the right, y down and z towards the eye, and the scene is built that way: the floor lies in the x-y plane facing the eye, heights are z.
6
- /** The viewpoints, in the order the controls and the number keys list them. */
6
+ /** A direction from anything: `"clockwise"` is clockwise, and everything else is the default, `"counterclockwise"`. */
7
+ export function turnDirection(value) {
8
+ return value === "clockwise" ? "clockwise" : "counterclockwise";
9
+ }
7
10
  export const TURN_PRESET_NAMES = ["overhead", "broadcast", "end", "corner"];
8
11
  /**
9
12
  * The four viewpoints. `overhead` looks straight down; `broadcast` is the main camera, high on one side line and turned a little towards the near end so that the boards, the nets and the goals show their faces; `end` stands behind an end line, along the
@@ -1,4 +1,4 @@
1
- import { type TurnPreset, type TurnView } from "./turn.camera.ts";
1
+ import { type TurnDirection, type TurnPreset, type TurnView } from "./turn.camera.ts";
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";
@@ -27,6 +27,10 @@ export interface KyogijoTurn<O = SurfaceOptions | ScoreOptions> {
27
27
  play(): boolean;
28
28
  /** Stop the auto-turn. */
29
29
  pause(): void;
30
+ /** Which way the auto-turn goes. */
31
+ direction(): TurnDirection;
32
+ /** Change which way the auto-turn goes, at once and without stopping it (the Reverse button does). */
33
+ setDirection(direction: TurnDirection): void;
30
34
  /** Start the auto-turn if it is stopped and stop it if it is running; it gives whether it is running now. */
31
35
  togglePlay(): boolean;
32
36
  /** The flat drawing that is the floor, or `null` after `destroy`. */
package/dist/turn.dom.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // reduced motion gets jumps for glides, and where the browser cannot draw in 3D the flat drawing stays. Nothing is polled and nothing runs while the view is
4
4
  // still: the elements' `transform`s are written when the view changes, and a glide between viewpoints is a short run of animation frames.
5
5
  import { drawKyogijo } from "./draw.js";
6
- import { STEP, TURN_PRESET_NAMES, clampView, drag, nearest, nudge, presetOf, presetView, step } from "./turn.camera.js";
6
+ import { STEP, TURN_PRESET_NAMES, clampView, drag, nearest, nudge, presetOf, presetView, step, turnDirection } from "./turn.camera.js";
7
7
  import { buildTurn, floorOptions, openingView, turnWords } from "./turn.model.js";
8
8
  import { controlsMarkup, layoutOf, turnMarkupOf, worldMarkup } from "./turn.markup.js";
9
9
  import { KYOGIJO_TURN_STYLE } from "./turn.style.js";
@@ -62,6 +62,8 @@ export function mountTurn(host, options, settings, build) {
62
62
  playing: false,
63
63
  play: () => false,
64
64
  pause: () => undefined,
65
+ direction: () => turnDirection(settings.direction),
66
+ setDirection: () => undefined,
65
67
  togglePlay: () => false,
66
68
  update: (next, change) => {
67
69
  if (change?.venue !== undefined)
@@ -93,6 +95,7 @@ export function mountTurn(host, options, settings, build) {
93
95
  let lastTick;
94
96
  let advanced = false;
95
97
  const speed = typeof settings.speed === "number" && Number.isFinite(settings.speed) ? settings.speed : 12;
98
+ let way = turnDirection(settings.direction);
96
99
  const calm = () => settings.motion === "none" || (settings.motion !== "smooth" && media?.matches === true);
97
100
  // The elements, floor first, and what was written to each, so that only a change is written.
98
101
  let elements = [];
@@ -236,7 +239,7 @@ export function mountTurn(host, options, settings, build) {
236
239
  lastTick = stamp;
237
240
  if (dt > 0) {
238
241
  stop();
239
- view = clampView({ ...view, yaw: view.yaw - speed * dt });
242
+ view = clampView({ ...view, yaw: view.yaw - (way === "clockwise" ? -1 : 1) * speed * dt });
240
243
  shown = view;
241
244
  advanced = true;
242
245
  paint(shown);
@@ -266,6 +269,16 @@ export function mountTurn(host, options, settings, build) {
266
269
  say(next ? turnWords(model).announcePlay : turnWords(model).announcePause);
267
270
  settings.onPlayChange?.(next);
268
271
  };
272
+ /** Change the direction; the Reverse button says which it is, and the page is told. */
273
+ const setWay = (next, quiet = false) => {
274
+ if (next === way)
275
+ return;
276
+ way = next;
277
+ root.querySelector("button[data-act=reverse]")?.setAttribute("aria-pressed", String(way === "clockwise"));
278
+ if (!quiet)
279
+ say(way === "clockwise" ? turnWords(model).announceClockwise : turnWords(model).announceCounterclockwise);
280
+ settings.onDirectionChange?.(way);
281
+ };
269
282
  // A view scrolled out of sight does not turn.
270
283
  let onScreen = true;
271
284
  let seen;
@@ -304,6 +317,7 @@ export function mountTurn(host, options, settings, build) {
304
317
  case "zoom-in": return go(step(view, { zoom: STEP.zoom }), 180, "none", true);
305
318
  case "zoom-out": return go(step(view, { zoom: 1 / STEP.zoom }), 180, "none", true);
306
319
  case "play": return void setPlaying(!playing);
320
+ case "reverse": return setWay(way === "clockwise" ? "counterclockwise" : "clockwise");
307
321
  case "reset": return toOpening();
308
322
  default:
309
323
  }
@@ -476,7 +490,7 @@ export function mountTurn(host, options, settings, build) {
476
490
  root.removeAttribute("data-theme");
477
491
  const controls = root.querySelector(".ky-turn-controls");
478
492
  if (controls !== null)
479
- controls.outerHTML = controlsMarkup(model, view, playing);
493
+ controls.outerHTML = controlsMarkup(model, view, playing, way);
480
494
  paint(shown);
481
495
  label();
482
496
  },
@@ -490,6 +504,10 @@ export function mountTurn(host, options, settings, build) {
490
504
  pause() {
491
505
  setPlaying(false);
492
506
  },
507
+ direction: () => way,
508
+ setDirection(next) {
509
+ setWay(turnDirection(next));
510
+ },
493
511
  togglePlay() {
494
512
  setPlaying(!playing);
495
513
  return playing;
@@ -1,4 +1,4 @@
1
- import { type Placement, type TurnView } from "./turn.camera.ts";
1
+ import { type Placement, type TurnDirection, type TurnView } from "./turn.camera.ts";
2
2
  import { type Plane } from "./turn.kit.ts";
3
3
  import { type TurnModel, type TurnSettings } from "./turn.model.ts";
4
4
  import type { ScoreOptions, SurfaceOptions } from "./types.ts";
@@ -45,7 +45,7 @@ export declare function worldAttributes(model: TurnModel): string;
45
45
  /** The floor and the planes of a model, laid out. */
46
46
  export declare function worldMarkup(model: TurnModel, layout: Layout): string;
47
47
  /** The buttons under the view. */
48
- export declare function controlsMarkup(model: TurnModel, view: TurnView, playing?: boolean): string;
48
+ export declare function controlsMarkup(model: TurnModel, view: TurnView, playing?: boolean, direction?: TurnDirection): string;
49
49
  /** The size, in pixels, the first view is worked out for. */
50
50
  export declare const sizeOf: (settings: TurnSettings) => {
51
51
  width: number;
@@ -2,7 +2,7 @@
2
2
  // `perspective` on a parent): every element carries its whole place on the screen in one `matrix3d`, worked out here from the way the scene is looked at, and
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
- import { TURN_PRESET_NAMES, cameraMatrix, depthOf, eyeDistance, fit, matrix3d, multiply, presetOf } from "./turn.camera.js";
5
+ import { TURN_PRESET_NAMES, cameraMatrix, depthOf, eyeDistance, fit, matrix3d, multiply, presetOf, turnDirection } from "./turn.camera.js";
6
6
  import { 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";
@@ -123,7 +123,7 @@ export function worldMarkup(model, layout) {
123
123
  return defsMarkup(model) + floorMarkup(model, layout.transforms[0], layout.stack[0]) + model.planes.map((p, i) => (p.crop === true ? cropMarkup(model, p, layout.transforms[i + 1], layout.stack[i + 1], layout.fade[i + 1]) : planeMarkup(p, model.scale, layout.transforms[i + 1], layout.stack[i + 1], layout.fade[i + 1]))).join("");
124
124
  }
125
125
  /** The buttons under the view. */
126
- export function controlsMarkup(model, view, playing = false) {
126
+ export function controlsMarkup(model, view, playing = false, direction = "counterclockwise") {
127
127
  const words = turnWords(model);
128
128
  const here = presetOf(view, model.presets ?? model.options.sport);
129
129
  const presets = TURN_PRESET_NAMES.map((name, i) => `<button type="button" data-preset="${name}" aria-pressed="${here === name}" aria-keyshortcuts="${i + 1}">${esc(words[name])}</button>`).join("");
@@ -138,7 +138,9 @@ export function controlsMarkup(model, view, playing = false) {
138
138
  const buttons = tools.map(([act, glyph, label]) => `<button type="button" data-act="${act}" aria-label="${esc(label)}" title="${esc(label)}">${glyph}</button>`).join("");
139
139
  // The auto-turn: a toggle named for what it is, pressed while the view turns; its glyph and tooltip say what pressing it does.
140
140
  const play = `<button type="button" data-act="play" data-wide aria-pressed="${playing}" aria-keyshortcuts="Space" title="${esc(playing ? words.playOff : words.playOn)}"><span data-glyph aria-hidden="true">${playing ? "❚❚" : "▶"}</span>${esc(words.play)}</button>`;
141
- return `<div class="ky-turn-controls"><div class="ky-turn-group" role="group" aria-label="${esc(words.group)}">${presets}</div><div class="ky-turn-group">${buttons}<button type="button" data-act="reset" aria-keyshortcuts="0 Home">${esc(words.reset)}</button>${play}</div></div>`;
141
+ // The other way round: a toggle, pressed while the auto-turn goes clockwise (the default is how it has always turned, the near side swinging to the right).
142
+ const reverse = `<button type="button" data-act="reverse" aria-pressed="${direction === "clockwise"}" aria-label="${esc(words.reverseLabel)}" title="${esc(words.reverseLabel)}">${esc(words.reverse)}</button>`;
143
+ return `<div class="ky-turn-controls"><div class="ky-turn-group" role="group" aria-label="${esc(words.group)}">${presets}</div><div class="ky-turn-group">${buttons}<button type="button" data-act="reset" aria-keyshortcuts="0 Home">${esc(words.reset)}</button>${play}${reverse}</div></div>`;
142
144
  }
143
145
  /** The size, in pixels, the first view is worked out for. */
144
146
  export const sizeOf = (settings) => settings.size ?? { width: 960, height: 600 };
@@ -156,7 +158,7 @@ export function turnMarkupOf(model, settings = {}) {
156
158
  sheet +
157
159
  `<div class="ky-turn-view" tabindex="0" role="group" aria-roledescription="${esc(words.region)}" aria-label="${esc(model.title)}" aria-describedby="${esc(model.id)}-help" aria-keyshortcuts="ArrowLeft ArrowRight ArrowUp ArrowDown + - 0 1 2 3 4 Home Space"><div ${worldAttributes(model)}>${worldMarkup(model, layout)}</div>${model.board === undefined ? "" : turnBoardMarkup(model.board)}</div>` +
158
160
  `<p class="ky-turn-help" id="${esc(model.id)}-help">${esc(keys)}</p>` +
159
- (settings.controls === false ? "" : controlsMarkup(model, view)) +
161
+ (settings.controls === false ? "" : controlsMarkup(model, view, false, turnDirection(settings.direction))) +
160
162
  `<div class="ky-turn-live" role="status" aria-live="polite"></div>` +
161
163
  `</div>`);
162
164
  }
@@ -1,5 +1,5 @@
1
1
  import { KYOGIJO_STRINGS } from "./strings.ts";
2
- import { type PresetTable, type TurnPreset, type TurnView } from "./turn.camera.ts";
2
+ import { type PresetTable, type TurnDirection, type TurnPreset, type TurnView } from "./turn.camera.ts";
3
3
  import type { Plane } from "./turn.kit.ts";
4
4
  import { type UprightOptions, type Uprights } from "./uprights.ts";
5
5
  import { type ResolvedBoard } from "./overlay.ts";
@@ -46,6 +46,10 @@ export interface TurnSettings {
46
46
  autoPlay?: boolean | "force";
47
47
  /** Degrees of yaw a second the auto-turn goes by: 12 by default, positive turning the near side to the right as a drag to the right does, negative the other way. */
48
48
  speed?: number;
49
+ /** Which way the auto-turn goes: `"counterclockwise"` (the way it has always turned, the near side swinging to the viewer's right) by default, or `"clockwise"`. The Reverse button, `setDirection` and `update` change it; a negative `speed` reverses it too. */
50
+ direction?: TurnDirection;
51
+ /** Called with the direction when the Reverse button or `setDirection` changes it. */
52
+ onDirectionChange?: (direction: TurnDirection) => void;
49
53
  /** Called with `true` when the auto-turn starts and `false` when it stops, whatever stopped it. */
50
54
  onPlayChange?: (playing: boolean) => void;
51
55
  /** Called after the view has changed by a key, a button or the pointer, with the view and the viewpoint it is (`undefined` when it has been turned by hand). */
@@ -155,7 +155,7 @@ export const KYOGIJO_TURN_STYLE = `
155
155
  .ky-turn-group{display:flex;flex-wrap:wrap;gap:6px}
156
156
  .ky-turn-controls button{font:inherit;font-size:.9rem;font-weight:600;min-width:44px;min-height:44px;padding:0 12px;border-radius:999px;border:1px solid var(--ky-turn-rule);background:var(--ky-turn-chip);color:var(--ky-turn-ink);cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
157
157
  .ky-turn-controls button[aria-pressed=true]{background:var(--ky-turn-chip-on);color:var(--ky-turn-chip-on-ink);border-color:var(--ky-turn-chip-on)}
158
- .ky-turn-controls button[data-act]:not([data-act=reset]):not([data-act=play]){font-size:1.2rem}
158
+ .ky-turn-controls button[data-act]:not([data-act=reset]):not([data-act=play]):not([data-act=reverse]){font-size:1.2rem}
159
159
  .ky-turn-controls button[data-act=play]{gap:.45em;padding:0 16px}
160
160
  .ky-turn-controls button[data-act=play] [data-glyph]{font-size:.8em;letter-spacing:-.1em}
161
161
  .ky-turn-controls button:focus-visible{outline:3px solid var(--ky-turn-ink);outline-offset:2px}
package/dist/types.d.ts CHANGED
@@ -73,11 +73,11 @@ export interface Placement {
73
73
  /** How much of the surface's markings are drawn. `simple` leaves out the small ones, such as hash marks. */
74
74
  export type Detail = "full" | "simple";
75
75
  /**
76
- * How grass is mown: bands across the surface (the usual), wide ones or narrow ones, along it, in checks, on the diagonal, in rings round the
76
+ * How grass is mown: bands across the surface (the usual), wide ones or narrow ones, along it, in checks, on the diagonal, in diamonds (the checks turned 45 degrees: a ballpark outfield's crosshatch, its sides along the foul lines), in rings round the
77
77
  * middle (`circles`), or `none`. A ballpark is mown in `rings` by default (arcs about home plate) and may also be mown in a `fan` (wedges out from
78
78
  * home plate). A football field and a ballpark take the same options as a soccer pitch; a surface that is not grass ignores them.
79
79
  */
80
- export type StripePattern = "bands" | "wide" | "narrow" | "lengthwise" | "checks" | "diagonal" | "circles" | "rings" | "fan" | "none";
80
+ export type StripePattern = "bands" | "wide" | "narrow" | "lengthwise" | "checks" | "diagonal" | "diamonds" | "circles" | "rings" | "fan" | "none";
81
81
  /**
82
82
  * How a goal is drawn, seen from above. A soccer goal: the net as a box behind the goal line, a line on it, a solid strip, or not at all.
83
83
  * A hockey net: the same four (`box` is the open frame behind the goal line, `strip` a filled net). A basketball basket: `box` is the backboard,
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.34.1";
2
+ export declare const VERSION = "0.36.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export const VERSION = "0.34.1";
2
+ export const VERSION = "0.36.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/kyogijo-stadiums",
3
- "version": "0.34.1",
3
+ "version": "0.36.0",
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",