@johnmorrisdotca/taisen-brackets 0.4.0 → 0.5.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/dist/text.js CHANGED
@@ -29,13 +29,13 @@ export function textWidth(text, size) {
29
29
  return em * size;
30
30
  }
31
31
  /** The text cut to fit a width, with an ellipsis where it was cut. */
32
- export function fit(text, size, width) {
33
- if (textWidth(text, size) <= width)
32
+ export function fit(text, size, width, measure = textWidth) {
33
+ if (measure(text, size) <= width)
34
34
  return text;
35
35
  const chars = [...text];
36
- while (chars.length > 1 && textWidth(`${chars.join("")}…`, size) > width)
36
+ while (chars.length > 1 && measure(`${chars.join("")}…`, size) > width)
37
37
  chars.pop();
38
- return chars.length <= 1 && textWidth(text, size) > width * 2 ? "…" : `${chars.join("").trimEnd()}…`;
38
+ return chars.length <= 1 && measure(text, size) > width * 2 ? "…" : `${chars.join("").trimEnd()}…`;
39
39
  }
40
40
  /** A short name for a small box: the initials of a name of several words ("NYR"), else its first three letters; capitals. */
41
41
  export function abbreviate(name) {
package/dist/theme.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { BackgroundInk } from "./family-backgrounds.ts";
1
2
  import type { ThemeName } from "./types.ts";
2
3
  /** The colours of one look. Each is the fallback of a CSS custom property of the same name: `--tb-ink` and so on. */
3
4
  export interface Palette {
@@ -31,10 +32,17 @@ export interface StyleParts {
31
32
  links?: boolean;
32
33
  /** A team's path is highlighted: the other ties are quieter and the path is dotted. */
33
34
  follow?: boolean;
35
+ /** A background with a light and a dark picture in one drawing: the one that does not fit the look is hidden. */
36
+ bgAuto?: boolean;
37
+ }
38
+ /** The colours a background says to draw in (`backgroundSvg`'s `ink`): what `styleFor` takes in place of a look's own palette. */
39
+ export interface BackgroundInks {
40
+ light?: BackgroundInk;
41
+ dark?: BackgroundInk;
34
42
  }
35
43
  /**
36
44
  * The `<style>` text of a drawing. `auto` is the light look and, under `prefers-color-scheme: dark` or a page that says
37
45
  * `data-theme="dark"` on `<html>`, the dark one; `light`, `dark` and `poster` are one look always. Every colour can be
38
46
  * set from the page with a `--tb-*` custom property. `parts` adds the rules the road look, the road chart, links and a highlighted path need.
39
47
  */
40
- export declare function styleFor(scope: string, theme: Exclude<ThemeName, "none">, parts?: StyleParts): string;
48
+ export declare function styleFor(scope: string, theme: Exclude<ThemeName, "none">, parts?: StyleParts, inks?: BackgroundInks): string;
package/dist/theme.js CHANGED
@@ -10,8 +10,15 @@ export const ROAD_THEMES = Object.freeze({
10
10
  poster: { ring: "#f6f1e3", edge: "rgba(0,0,0,.5)", panel: "#1b2766", spine: "#d9b24c" },
11
11
  });
12
12
  const FONT = 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Hiragino Sans", "Yu Gothic UI", "Noto Sans CJK JP", Meiryo, sans-serif';
13
+ /** A palette and a road palette from a background's ink. */
14
+ function fromInk(ink) {
15
+ return [
16
+ { bg: ink.base, ink: ink.text, muted: ink.muted, line: ink.line, box: ink.box, edge: ink.edge, win: ink.accent, gold: ink.accent, plain: ink.plain },
17
+ { ring: ink.ring, edge: ink.dark ? "rgba(0,0,0,.55)" : "rgba(0,0,0,.35)", panel: ink.panel, spine: ink.spine },
18
+ ];
19
+ }
13
20
  /** The rules for one palette, each colour read from its custom property with the palette's value as the fallback. */
14
- function rules(p, r, poster, withFont, parts) {
21
+ function rules(p, r, poster, withFont, parts, side) {
15
22
  const v = (name) => `var(--tb-${name}, ${p[name]})`;
16
23
  const rv = (name, css) => `var(--tb-${css}, ${r[name]})`;
17
24
  const road = parts.road === true || parts.chart === true;
@@ -38,7 +45,7 @@ function rules(p, r, poster, withFont, parts) {
38
45
  [[".tb-halo"], `paint-order:stroke;stroke:${v("bg")};stroke-width:4px;stroke-linejoin:round`],
39
46
  ];
40
47
  if (road) {
41
- list.push([[".tb-edge"], `fill:none;stroke:${rv("edge", "mark-edge")}`], [[".tb-lost .tb-ring-r", ".tb-lost .tb-edge"], "opacity:.55"], [[".tb-panel"], `fill:${rv("panel", "panel")}`], [[".tb-lab"], `fill:${v("ink")};font-weight:700`], [[".tb-spine"], `stroke:${rv("spine", "spine")};fill:none;stroke-linecap:round`]);
48
+ list.push([[".tb-edge"], `fill:none;stroke:${rv("edge", "mark-edge")}`], [[".tb-lost .tb-ring-r", ".tb-lost .tb-edge"], "opacity:.55"], [[".tb-panel"], `fill:${rv("panel", "panel")}`], [[".tb-lab"], `fill:${v("ink")};font-weight:700`], [[".tb-spine"], `stroke:${rv("spine", "spine")};fill:none;stroke-linecap:round`], [[".tb-tagbox"], `fill:${rv("panel", "panel")}`], [[".tb-tag"], `fill:${v("ink")};font-weight:700`], [[".tb-code", ".tb-vs"], `fill:${v("muted")};font-weight:700`]);
42
49
  }
43
50
  if (parts.road === true) {
44
51
  list.push([[".tb-name"], "font-weight:700"], [[".tb-score"], "font-weight:800"], [[".tb-lost .tb-score"], `fill:${v("muted")};opacity:1`], [[".tb-line"], `stroke-linecap:round;stroke-linejoin:round;stroke-width:${poster ? 2 : 1.8};stroke-opacity:.55`]);
@@ -52,6 +59,10 @@ function rules(p, r, poster, withFont, parts) {
52
59
  if (parts.links === true) {
53
60
  list.push([[".tb-link"], "cursor:pointer;outline:none"], [[".tb-link:hover .tb-box", ".tb-link:focus-visible .tb-box"], `stroke:${v("win")};stroke-width:2.5`], [[".tb-link:hover .tb-name", ".tb-link:focus-visible .tb-name"], "text-decoration:underline"], [[".tb-link:focus-visible"], `outline:2px solid ${v("win")};outline-offset:2px`]);
54
61
  }
62
+ if (parts.bgAuto === true)
63
+ list.push(side === "dark" ? [[".tb-bgl"], "display:none"] : [[".tb-bgd"], "display:none"]);
64
+ if (parts.bgAuto === true && side === "dark")
65
+ list.push([[".tb-bgd"], "display:inline"]);
55
66
  if (withFont)
56
67
  list.push([["text"], `font-family:var(--tb-font, ${FONT})`]);
57
68
  return list;
@@ -62,16 +73,20 @@ const write = (scope, list, before = "") => list.map(([sel, body]) => `${sel.map
62
73
  * `data-theme="dark"` on `<html>`, the dark one; `light`, `dark` and `poster` are one look always. Every colour can be
63
74
  * set from the page with a `--tb-*` custom property. `parts` adds the rules the road look, the road chart, links and a highlighted path need.
64
75
  */
65
- export function styleFor(scope, theme, parts = {}) {
76
+ export function styleFor(scope, theme, parts = {}, inks = {}) {
77
+ // A look takes the ink of the picture drawn for it; a picture asked for in the other variant (`background.theme`) gives its ink to both.
78
+ const only = inks.light ?? inks.dark;
79
+ const [lp, lr] = (inks.light ?? only) ? fromInk((inks.light ?? only)) : [THEMES.light, ROAD_THEMES.light];
80
+ const [dp, dr] = (inks.dark ?? only) ? fromInk((inks.dark ?? only)) : [THEMES.dark, ROAD_THEMES.dark];
66
81
  if (theme === "light")
67
- return write(scope, rules(THEMES.light, ROAD_THEMES.light, false, true, parts));
82
+ return write(scope, rules(lp, lr, false, true, parts, "light"));
68
83
  if (theme === "dark")
69
- return write(scope, rules(THEMES.dark, ROAD_THEMES.dark, false, true, parts));
84
+ return write(scope, rules(dp, dr, false, true, parts, "dark"));
70
85
  if (theme === "poster")
71
- return write(scope, rules(THEMES.poster, ROAD_THEMES.poster, true, true, parts));
72
- const dark = rules(THEMES.dark, ROAD_THEMES.dark, false, false, parts);
86
+ return write(scope, only ? rules(dp, dr, true, true, parts, "dark") : rules(THEMES.poster, ROAD_THEMES.poster, true, true, parts, "dark"));
87
+ const dark = rules(dp, dr, false, false, parts, "dark");
73
88
  return [
74
- write(scope, rules(THEMES.light, ROAD_THEMES.light, false, true, parts)),
89
+ write(scope, rules(lp, lr, false, true, parts, "light")),
75
90
  `@media (prefers-color-scheme: dark){\n${write(scope, dark, ':root:not([data-theme="light"]) ')}\n}`,
76
91
  write(scope, dark, ':root[data-theme="dark"] '),
77
92
  ].join("\n");
package/dist/types.d.ts CHANGED
@@ -12,6 +12,8 @@ export interface Team {
12
12
  abbr?: string;
13
13
  /** The seed, shown beside the name when `seeds` is on. */
14
14
  seed?: number;
15
+ /** A tag for the road look's seed tags (`C1`, `WC2`, `M3`): written small beside the mark when `seeds` is on, in place of the number. A tie's `seeds` win over it. */
16
+ tag?: string;
15
17
  /**
16
18
  * The team's mark: a logo or a flag. Either the address of an image (`https://`, `data:image/…`, or a relative
17
19
  * path) or inline SVG markup (`<svg …>`; a round flag from `@johnmorrisdotca/hata` goes here as it is).
@@ -100,6 +102,8 @@ export interface Matchup {
100
102
  /** This is the third-place match: it leads nowhere and is drawn apart. */
101
103
  third?: boolean;
102
104
  note?: string;
105
+ /** The seed tags of the two sides for this tie (`["C1", "WC2"]`), written beside the marks in the road look when `seeds` is on; a side's `Team.tag` is used where this has none. */
106
+ seeds?: readonly [string | number | null, string | number | null];
103
107
  }
104
108
  /** What a tie came to, worked out from its games. */
105
109
  export interface Result {
@@ -134,6 +138,8 @@ export interface Tie {
134
138
  third: boolean;
135
139
  walkover: boolean;
136
140
  note: string | null;
141
+ /** The seed tags given for the tie's sides (`Matchup.seeds`). */
142
+ seeds?: readonly [string | number | null, string | number | null];
137
143
  }
138
144
  /** Where the layout put a tie. */
139
145
  export type Place = "l" | "r" | "f" | "o";
@@ -343,6 +349,29 @@ export interface EmblemRequest {
343
349
  }
344
350
  /** A function from an emblem id and a request to an SVG string; `renderEmblem` of `@johnmorrisdotca/dougu-sports/emblems` is one. */
345
351
  export type EmblemRenderer = (id: string, options: EmblemRequest) => string;
352
+ /** A background preset's name. */
353
+ export type { BackgroundPresetName } from "./family-backgrounds.ts";
354
+ import type { BackgroundPresetName } from "./family-backgrounds.ts";
355
+ /** A preset, tuned: your own colours (hex or `rgb()`, the first two the ground), the gradient's angle, how strong the pattern is (0 to 1, default 0.6), a seed for the scattered parts, and which of its two variants. */
356
+ export interface BackgroundOptions {
357
+ /** Default `midnight`. */
358
+ preset?: BackgroundPresetName;
359
+ colors?: readonly string[];
360
+ angle?: number;
361
+ intensity?: number;
362
+ seed?: number;
363
+ /** `light` or `dark`: the variant to draw. By default the one of the drawing's look. */
364
+ theme?: "light" | "dark";
365
+ }
366
+ /** Your own picture as the background: an image address (http(s), relative or `data:image/…`; anything else is refused), laid under a veil so the text reads. */
367
+ export interface BackgroundImageOptions {
368
+ image: string;
369
+ /** `cover` (the default) fills the drawing; `contain` shows all of the picture. */
370
+ fit?: "cover" | "contain";
371
+ /** How dark (or, in a light look, how pale) the veil is, 0 to 1. Default 0.5. */
372
+ dim?: number;
373
+ theme?: "light" | "dark";
374
+ }
346
375
  export interface DrawOptions extends LayoutOptions {
347
376
  /** `wide` 1200 by 675, `square` 1080 by 1080, `card` 1080 by 1350 (a tall post), `phone` 390 wide and as tall as it needs; or a size of your own. Default `wide`. */
348
377
  size?: SizeName | {
@@ -354,8 +383,16 @@ export interface DrawOptions extends LayoutOptions {
354
383
  detail?: Detail;
355
384
  /** `auto` follows the page's light or dark; `poster` is dark with gold lines; `none` writes no style, for your own CSS. Default `auto`. */
356
385
  theme?: ThemeName;
357
- /** Draw a background. Default true; false for a transparent drawing. */
358
- background?: boolean;
386
+ /**
387
+ * The background. `true` (the default) is the look's plain colour and `false` a transparent drawing. A preset's name (`"midnight"`, `"ice"`, see `BACKGROUND_PRESETS`) draws that
388
+ * poster background in the look's light or dark variant (`auto` draws both and shows the one that fits the page), and the text, lines, boxes and marks take the colours that
389
+ * read over it. An object tunes a preset (`{ preset, colors, angle, intensity, seed }`) or draws your own picture (`{ image, fit, dim }`).
390
+ */
391
+ background?: boolean | BackgroundPresetName | BackgroundOptions | BackgroundImageOptions;
392
+ /** In the road look, write a small `vs` between the two marks of a box of marks alone. Default false. */
393
+ vs?: boolean;
394
+ /** In the road look, write each team's three-letter code under its mark in a box of marks alone. Default false for a bracket (a chart's `codes` is on by default). */
395
+ codes?: boolean;
359
396
  title?: string;
360
397
  subtitle?: string;
361
398
  /** Names of the rounds, first round first (or just the last ones). Default: Final, Semi-finals, Quarter-finals, Round of 16… */
@@ -368,7 +405,7 @@ export interface DrawOptions extends LayoutOptions {
368
405
  roundLabels?: boolean;
369
406
  /** Show the scores. Default true. */
370
407
  scores?: boolean;
371
- /** Show seeds before names. Default false. */
408
+ /** Show seeds: the number before the name in the classic look, and in the road look a small tag (`C1`, `WC2`: `Matchup.seeds`, else `Team.tag`, else `Team.seed`) beside each mark. Default false. */
372
409
  seeds?: boolean;
373
410
  /** `round` (the default), `square`, or `none` (names only). */
374
411
  markShape?: "round" | "square" | "none";
@@ -394,6 +431,16 @@ export interface DrawOptions extends LayoutOptions {
394
431
  * made from `@johnmorrisdotca/hata`. With none, or when it returns nothing for a code, the team's `mark` or its initials are drawn.
395
432
  */
396
433
  flags?: FlagRenderer;
434
+ /**
435
+ * The address of the flag a team's `flag` code names, for a flag that is a picture to load (a file on your own server) and not SVG to inline, which can be 80 kB for one flag. Tried where `flags` gives
436
+ * none; the address must be http(s), relative or `data:image/…`, and is written once as a symbol however many times the flag appears.
437
+ */
438
+ flagHref?: (code: string) => string | null | undefined;
439
+ /**
440
+ * A class for each tie's group, from the tie: `(tie) => tie.sides.some(inDivision) ? "mine" : "other"`. A word of letters, digits, `-` and `_` is added to the group's classes (anything else is left out),
441
+ * so that a page's own style can highlight or dim ties: `.mine .tb-box { stroke: … }`. `undefined` or an empty text adds none.
442
+ */
443
+ tieClass?: (tie: Tie) => string | null | undefined;
397
444
  /**
398
445
  * `classic` (the default) or `road`: the look of the road-to-the-final chart, with large round marks, big bold scores, the loser quiet, the
399
446
  * rounds named on pills and a dotted path. Every other option works in both.
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.4.0";
1
+ export declare const VERSION = "0.5.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.4.0";
1
+ export const VERSION = "0.5.0";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/taisen-brackets",
3
- "version": "0.4.0",
4
- "description": "Taisen Brackets 対戦, knockout brackets as SVG: from a list of ties (games, best-of series, two legs, replays, byes, a third-place match) it works out the rounds and halves and draws the bracket with the halves meeting at the final, or left to right, wide, square, as a tall card or for a phone, in a classic or a road look (large round flags, big scores, a team's dotted path, a link on every box), in light, dark or a gold-on-navy poster look, with each team's logo or round flag, the champion in the centre, and an accessible table; and the road to the final of either finalist. NHL, NBA, MLB, NFL, CFL, MLS and World Cup formats by era, in English, French, Japanese, Spanish and German. Zero dependencies.",
3
+ "version": "0.5.0",
4
+ "description": "Taisen Brackets 対戦, knockout brackets as SVG: from a list of ties (games, best-of series, two legs, replays, byes, a third-place match) it works out the rounds and halves and draws the bracket with the halves meeting at the final, or left to right, wide, square, as a tall card or for a phone, in a classic or a road look (large round flags, big scores, a team's dotted path, a link on every box), in light, dark or a gold-on-navy poster look, on one of twenty-five poster backgrounds (the playing surfaces of five sports among them) or your own, with each team's logo or round flag, the champion in the centre, and an accessible table; and the road to the final of either finalist. NHL, NBA, MLB, NFL, CFL, MLS and World Cup formats by era, in English, French, Japanese, Spanish and German. Zero dependencies.",
5
5
  "keywords": [
6
6
  "bracket",
7
7
  "tournament",
@@ -67,6 +67,11 @@
67
67
  "types": "./dist/road-entry.d.ts",
68
68
  "import": "./dist/road-entry.js",
69
69
  "default": "./dist/road-entry.js"
70
+ },
71
+ "./backgrounds": {
72
+ "types": "./dist/backgrounds-entry.d.ts",
73
+ "import": "./dist/backgrounds-entry.js",
74
+ "default": "./dist/backgrounds-entry.js"
70
75
  }
71
76
  },
72
77
  "files": [
@@ -93,6 +98,7 @@
93
98
  "flags": "node scripts/make-flags.mjs",
94
99
  "site": "pnpm build && node scripts/site.mjs",
95
100
  "test:demo": "pnpm site && playwright test",
101
+ "images": "pnpm site && node scripts/screenshots.mjs",
96
102
  "screenshots": "pnpm site && node scripts/screenshots.mjs"
97
103
  },
98
104
  "peerDependencies": {