@johnmorrisdotca/dougu-sports 0.1.0 → 0.2.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 +53 -1
- package/NOTICE.md +9 -58
- package/README.md +174 -37
- package/dist/emblems/award/badge.d.ts +2 -2
- package/dist/emblems/award/badge.js +14 -5
- package/dist/emblems/award/crown.d.ts +1 -1
- package/dist/emblems/award/crown.js +14 -4
- package/dist/emblems/award/medal.d.ts +2 -2
- package/dist/emblems/award/medal.js +14 -5
- package/dist/emblems/award/shield.d.ts +1 -1
- package/dist/emblems/award/shield.js +10 -4
- package/dist/emblems/award/star.d.ts +1 -1
- package/dist/emblems/award/star.js +10 -4
- package/dist/emblems/ball/ball.afl.js +3 -2
- package/dist/emblems/ball/ball.basketball6.js +1 -1
- package/dist/emblems/ball/ball.core.d.ts +9 -3
- package/dist/emblems/ball/ball.core.js +13 -0
- package/dist/emblems/ball/ball.cricket.js +1 -1
- package/dist/emblems/ball/ball.curlingstone.d.ts +2 -0
- package/dist/emblems/ball/ball.curlingstone.js +11 -7
- package/dist/emblems/ball/ball.discus.js +1 -1
- package/dist/emblems/ball/ball.engine.js +3 -3
- package/dist/emblems/ball/ball.football.js +9 -7
- package/dist/emblems/ball/ball.gaelic.js +1 -1
- package/dist/emblems/ball/ball.object.d.ts +5 -2
- package/dist/emblems/ball/ball.object.js +3 -2
- package/dist/emblems/ball/ball.puck.d.ts +2 -0
- package/dist/emblems/ball/ball.puck.js +2 -2
- package/dist/emblems/ball/ball.rugby.js +3 -2
- package/dist/emblems/ball/ball.shuttlecock.d.ts +4 -0
- package/dist/emblems/ball/ball.shuttlecock.js +6 -5
- package/dist/emblems/ball/ball.squash.js +5 -5
- package/dist/emblems/emblem.api.d.ts +39 -1
- package/dist/emblems/emblem.api.js +20 -2
- package/dist/emblems/emblem.compose.d.ts +31 -0
- package/dist/emblems/emblem.compose.js +91 -0
- package/dist/emblems/emblem.core.d.ts +48 -2
- package/dist/emblems/emblem.core.js +30 -0
- package/dist/emblems/emblem.descriptions.d.ts +7 -0
- package/dist/emblems/emblem.descriptions.js +60 -0
- package/dist/emblems/emblem.display.d.ts +75 -0
- package/dist/emblems/emblem.display.js +214 -0
- package/dist/emblems/emblem.engrave.d.ts +50 -0
- package/dist/emblems/emblem.engrave.js +145 -0
- package/dist/emblems/emblem.equipment.d.ts +24 -0
- package/dist/emblems/emblem.equipment.js +57 -0
- package/dist/emblems/emblem.flip.d.ts +10 -0
- package/dist/emblems/emblem.flip.js +46 -0
- package/dist/emblems/emblem.ids.d.ts +19 -11
- package/dist/emblems/emblem.ids.js +21 -6
- package/dist/emblems/emblem.lathe.d.ts +3 -0
- package/dist/emblems/emblem.lathe.js +26 -4
- package/dist/emblems/emblem.medal.js +4 -4
- package/dist/emblems/emblem.pennant.d.ts +3 -0
- package/dist/emblems/emblem.pennant.js +79 -0
- package/dist/emblems/emblem.registry.js +20 -0
- package/dist/emblems/emblem.shapes.d.ts +19 -2
- package/dist/emblems/emblem.shapes.js +55 -13
- package/dist/emblems/emblem.sprite.d.ts +38 -0
- package/dist/emblems/emblem.sprite.js +65 -0
- package/dist/emblems/emblem.uri.d.ts +22 -0
- package/dist/emblems/emblem.uri.js +29 -0
- package/dist/emblems/gear/gear.badmintonracket.d.ts +1 -1
- package/dist/emblems/gear/gear.badmintonracket.js +18 -14
- package/dist/emblems/gear/gear.baseballglove.d.ts +1 -1
- package/dist/emblems/gear/gear.baseballglove.js +20 -14
- package/dist/emblems/gear/gear.budo.d.ts +17 -0
- package/dist/emblems/gear/gear.budo.js +60 -0
- package/dist/emblems/gear/gear.core.d.ts +12 -31
- package/dist/emblems/gear/gear.core.js +61 -54
- package/dist/emblems/gear/gear.cricketbat.d.ts +1 -1
- package/dist/emblems/gear/gear.cricketbat.js +16 -14
- package/dist/emblems/gear/gear.fieldhockeystick.d.ts +1 -1
- package/dist/emblems/gear/gear.fieldhockeystick.js +15 -14
- package/dist/emblems/gear/gear.footballhelmet.d.ts +1 -1
- package/dist/emblems/gear/gear.footballhelmet.js +15 -14
- package/dist/emblems/gear/gear.goaliemask.d.ts +1 -1
- package/dist/emblems/gear/gear.goaliemask.js +22 -16
- package/dist/emblems/gear/gear.goalnet.d.ts +1 -1
- package/dist/emblems/gear/gear.goalnet.js +24 -14
- package/dist/emblems/gear/gear.hockeystick.d.ts +1 -1
- package/dist/emblems/gear/gear.hockeystick.js +14 -14
- package/dist/emblems/gear/gear.index.js +5 -1
- package/dist/emblems/gear/gear.kendo.d.ts +2 -0
- package/dist/emblems/gear/gear.kendo.js +41 -0
- package/dist/emblems/gear/gear.kyudo.d.ts +2 -0
- package/dist/emblems/gear/gear.kyudo.js +31 -0
- package/dist/emblems/gear/gear.lacrossestick.d.ts +1 -1
- package/dist/emblems/gear/gear.lacrossestick.js +25 -14
- package/dist/emblems/gear/gear.skates.js +1 -1
- package/dist/emblems/gear/gear.sticks.js +2 -2
- package/dist/emblems/gear/gear.sumo.d.ts +2 -0
- package/dist/emblems/gear/gear.sumo.js +43 -0
- package/dist/emblems/gear/gear.tabletennispaddle.d.ts +1 -1
- package/dist/emblems/gear/gear.tabletennispaddle.js +15 -14
- package/dist/emblems/gear/gear.tennisracket.d.ts +1 -1
- package/dist/emblems/gear/gear.tennisracket.js +16 -14
- package/dist/emblems/pennant/championship.d.ts +2 -0
- package/dist/emblems/pennant/championship.js +4 -0
- package/dist/emblems/sport/afl.js +1 -1
- package/dist/emblems/sport/artisticswimming.js +1 -1
- package/dist/emblems/sport/cfl.d.ts +1 -1
- package/dist/emblems/sport/cfl.js +4 -4
- package/dist/emblems/sport/cricket.d.ts +1 -1
- package/dist/emblems/sport/cricket.js +13 -4
- package/dist/emblems/sport/fieldhockey.js +1 -1
- package/dist/emblems/sport/gaelic.js +1 -1
- package/dist/emblems/sport/handball.d.ts +1 -1
- package/dist/emblems/sport/handball.js +14 -4
- package/dist/emblems/sport/ifaf.d.ts +1 -1
- package/dist/emblems/sport/ifaf.js +4 -4
- package/dist/emblems/sport/judo.d.ts +2 -0
- package/dist/emblems/sport/judo.js +13 -0
- package/dist/emblems/sport/karate.d.ts +2 -0
- package/dist/emblems/sport/karate.js +13 -0
- package/dist/emblems/sport/kendo.d.ts +2 -0
- package/dist/emblems/sport/kendo.js +14 -0
- package/dist/emblems/sport/kyudo.d.ts +2 -0
- package/dist/emblems/sport/kyudo.js +15 -0
- package/dist/emblems/sport/mlb.d.ts +1 -1
- package/dist/emblems/sport/mlb.js +27 -4
- package/dist/emblems/sport/nba.d.ts +1 -1
- package/dist/emblems/sport/nba.js +11 -4
- package/dist/emblems/sport/netball.js +1 -1
- package/dist/emblems/sport/nfl.d.ts +2 -1
- package/dist/emblems/sport/nfl.js +14 -4
- package/dist/emblems/sport/nhl.d.ts +1 -1
- package/dist/emblems/sport/nhl.js +12 -4
- package/dist/emblems/sport/pickleball.js +1 -1
- package/dist/emblems/sport/racquetball.js +1 -1
- package/dist/emblems/sport/roundnet.js +1 -1
- package/dist/emblems/sport/rugby.d.ts +1 -1
- package/dist/emblems/sport/rugby.js +11 -4
- package/dist/emblems/sport/sepaktakraw.js +1 -1
- package/dist/emblems/sport/soccer.d.ts +1 -1
- package/dist/emblems/sport/soccer.js +30 -4
- package/dist/emblems/sport/squash.js +1 -1
- package/dist/emblems/sport/sumo.d.ts +2 -0
- package/dist/emblems/sport/sumo.js +15 -0
- package/dist/emblems/sport/tabletennis.js +1 -1
- package/dist/emblems/sport/tennis.d.ts +1 -1
- package/dist/emblems/sport/tennis.js +14 -4
- package/dist/emblems/sport/teqball.js +1 -1
- package/dist/emblems/sport/volleyball.d.ts +1 -1
- package/dist/emblems/sport/volleyball.js +24 -4
- package/dist/emblems/trophy/banner.d.ts +2 -0
- package/dist/emblems/trophy/banner.js +20 -0
- package/dist/emblems/trophy/baseball.js +1 -0
- package/dist/emblems/trophy/basketball.js +1 -0
- package/dist/emblems/trophy/belt.js +2 -1
- package/dist/emblems/trophy/bowl.js +1 -0
- package/dist/emblems/trophy/canadian-bowl.js +1 -0
- package/dist/emblems/trophy/championship-banner.d.ts +2 -0
- package/dist/emblems/trophy/championship-banner.js +67 -0
- package/dist/emblems/trophy/cricket-urn.js +1 -0
- package/dist/emblems/trophy/cup.d.ts +1 -1
- package/dist/emblems/trophy/cup.js +15 -4
- package/dist/emblems/trophy/football.js +2 -1
- package/dist/emblems/trophy/globe.js +1 -0
- package/dist/emblems/trophy/hockey-cup.js +2 -1
- package/dist/emblems/trophy/lidded-cup.js +1 -0
- package/dist/emblems/trophy/plaque.js +2 -1
- package/dist/emblems/trophy/ring.d.ts +2 -0
- package/dist/emblems/trophy/ring.js +30 -0
- package/dist/emblems/trophy/rugby-cup.js +1 -0
- package/dist/emblems/trophy/salver.js +1 -0
- package/dist/emblems/trophy/soccer-ball.js +2 -1
- package/dist/emblems/trophy/soccer-globe.js +1 -0
- package/dist/emblems/trophy/sumo-cup.d.ts +2 -0
- package/dist/emblems/trophy/sumo-cup.js +28 -0
- package/dist/emblems/trophy/tennis-cup.js +2 -1
- package/dist/emblems/trophy/tennis-plate.js +1 -0
- package/dist/emblems-entry.d.ts +15 -6
- package/dist/emblems-entry.js +7 -2
- package/dist/index.d.ts +17 -0
- package/dist/index.js +15 -0
- package/dist/turn-control.d.ts +101 -0
- package/dist/turn-control.js +363 -0
- package/dist/turn-entry.d.ts +2 -0
- package/dist/turn-entry.js +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -1
- package/LICENSES/Apache-2.0.txt +0 -202
- package/LICENSES/CC-BY-3.0.txt +0 -319
- package/LICENSES/upstream/game-icons.license.txt +0 -45
- package/LICENSES/upstream/noto-emoji-2D-svg.LICENSE +0 -13
- package/LICENSES/upstream/pictogrammers-mdi.LICENSE +0 -20
- package/dist/emblems/emblem.material.d.ts +0 -9
- package/dist/emblems/emblem.material.data.d.ts +0 -34
- package/dist/emblems/emblem.material.data.js +0 -85
- package/dist/emblems/emblem.material.js +0 -23
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// A championship pennant: a long triangle of felt on a dowel, with an edge sewn in a second colour and the words and the year in felt letters. Drawn felt-flat in `metal` and `color` (the fibres are a
|
|
2
|
+
// faint speckle, the `metal` style adds a soft light), and as one outline in `line`. Turned, it flips like a coin and shows its back, which is plain felt and the stitching, with the dowel on the other
|
|
3
|
+
// side. Imports the core, the text widths of the engraving helpers and the coin-flip turn.
|
|
4
|
+
import { degrees, escape, hexOf, mix, n2, readColour, resolve, wrap } from "./emblem.core.js";
|
|
5
|
+
import { engravingOf, widthOf } from "./emblem.engrave.js";
|
|
6
|
+
import { flipSvg } from "./emblem.flip.js";
|
|
7
|
+
const FELT = "#8f1d2c";
|
|
8
|
+
const CREAM = "#f4f0e4";
|
|
9
|
+
const FONT = "system-ui,-apple-system,'Segoe UI','Hiragino Sans','Noto Sans JP','Yu Gothic',sans-serif";
|
|
10
|
+
/** The felt: a wide left edge on the dowel, a point at the right, edges that sag a little. */
|
|
11
|
+
const SHAPE = "M8 15Q34 22 60 32Q34 42 8 49Z";
|
|
12
|
+
const STITCH = "M10.2 18Q34 24.6 55.6 32Q34 39.4 10.2 46Z";
|
|
13
|
+
const ROD = "M4.6 11.5h3.4v41h-3.4z";
|
|
14
|
+
/** The half height of the felt at x. */
|
|
15
|
+
const half = (x) => 17 - ((x - 8) * 17) / 52;
|
|
16
|
+
/** The lines of lettering, each as large as fits inside the triangle from the left edge: the main words, then the year. */
|
|
17
|
+
function lettering(rows) {
|
|
18
|
+
if (rows.length === 0)
|
|
19
|
+
return [];
|
|
20
|
+
const x0 = 12.5;
|
|
21
|
+
for (let f = 11; f >= 0.8; f -= 0.2) {
|
|
22
|
+
const sizes = rows.map((_, i) => (i === 0 ? f : f * 0.55));
|
|
23
|
+
const total = sizes.reduce((sum, s) => sum + s * 1.12, 0);
|
|
24
|
+
let top = 32 - total / 2;
|
|
25
|
+
const placed = rows.map((t, i) => {
|
|
26
|
+
const size = sizes[i];
|
|
27
|
+
const y = top + size * 0.86;
|
|
28
|
+
top += size * 1.12;
|
|
29
|
+
return { text: t, x: x0, y, size, width: widthOf(t) * size };
|
|
30
|
+
});
|
|
31
|
+
const fits = placed.every((p) => {
|
|
32
|
+
const end = p.x + p.width;
|
|
33
|
+
const reach = half(end) - 3;
|
|
34
|
+
return end <= 56 && p.y - p.size * 0.8 >= 32 - reach && p.y + p.size * 0.04 <= 32 + reach;
|
|
35
|
+
});
|
|
36
|
+
if (fits)
|
|
37
|
+
return placed;
|
|
38
|
+
}
|
|
39
|
+
return rows.map((t, i) => ({ text: t, x: x0, y: 32 + i * 5, size: 2.2, width: widthOf(t) * 2.2 }));
|
|
40
|
+
}
|
|
41
|
+
/** One side of the pennant as a document. */
|
|
42
|
+
function draw(title, o, back) {
|
|
43
|
+
const r = resolve(o);
|
|
44
|
+
const line = r.style === "line";
|
|
45
|
+
const felt = r.tint ?? readColour(FELT);
|
|
46
|
+
const trim = r.accent ?? CREAM;
|
|
47
|
+
const ink = hexOf(readColour(o?.numberColor) ?? readColour(trim));
|
|
48
|
+
// The lettering is the engraving of the options: the free text (one line a row) or the rank and label, then the year, in capitals.
|
|
49
|
+
const e = engravingOf(o);
|
|
50
|
+
const rows = [...(e.free !== undefined ? e.free.split("\n") : [[e.rank, e.label].filter((part) => part !== undefined).join(" ")]), e.year ?? ""].map((t) => t.toUpperCase()).filter((t) => t !== "");
|
|
51
|
+
const flatFelt = hexOf(felt);
|
|
52
|
+
const pid = `dgpf${flatFelt.slice(1)}`;
|
|
53
|
+
let body = `<g${back ? ' transform="translate(64 0) scale(-1 1)"' : ""}>`;
|
|
54
|
+
body += `<path d="${ROD}" fill="${line ? "none" : "#a8743c"}"/>`;
|
|
55
|
+
body += `<path d="${SHAPE}" fill="${line ? "none" : r.style === "metal" ? `url(#${pid})` : flatFelt}"/>`;
|
|
56
|
+
if (!line && r.style === "metal")
|
|
57
|
+
body += `<path d="${SHAPE}" fill="url(#dgsp)" stroke="none"/>`;
|
|
58
|
+
body += `<path d="${STITCH}" fill="none" stroke="${line ? "currentColor" : trim}" stroke-width="${line ? 1.2 : 0.7}" stroke-dasharray="1.9 1.5" stroke-linecap="butt"/>`;
|
|
59
|
+
const text = back ? [] : lettering(rows);
|
|
60
|
+
if (text.length > 0) {
|
|
61
|
+
body += `<g font-family="${FONT}" font-weight="800" fill="${line ? "currentColor" : ink}" stroke="none">${text
|
|
62
|
+
.map((t) => `<text x="${n2(t.x)}" y="${n2(t.y)}" font-size="${n2(t.size)}" textLength="${n2(t.width)}" lengthAdjust="spacingAndGlyphs">${escape(t.text)}</text>`)
|
|
63
|
+
.join("")}</g>`;
|
|
64
|
+
}
|
|
65
|
+
body += "</g>";
|
|
66
|
+
const defs = r.style !== "metal"
|
|
67
|
+
? ""
|
|
68
|
+
: `<defs><pattern id="${pid}" width="2.4" height="2.4" patternUnits="userSpaceOnUse"><rect width="2.4" height="2.4" fill="${flatFelt}" stroke="none"/><circle cx=".6" cy=".7" r=".32" fill="#fff" fill-opacity=".14" stroke="none"/><circle cx="1.8" cy="1.6" r=".3" fill="#000" fill-opacity=".14" stroke="none"/></pattern><linearGradient id="dgsp" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fff" stop-opacity=".22"/><stop offset=".5" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity=".28"/></linearGradient></defs>`;
|
|
69
|
+
return wrap({ title, size: r.size, style: r.style, theme: r.theme, defs, body, edge: mix(felt, -0.5), width: 0.8 });
|
|
70
|
+
}
|
|
71
|
+
/** A pennant: its front with the lettering, turned like a coin to its plain back. */
|
|
72
|
+
export function definePennant(id, en, ja) {
|
|
73
|
+
const svg = (o) => draw(en, o, o?.view === "back");
|
|
74
|
+
const frame = (o) => {
|
|
75
|
+
const back = o?.view === "back";
|
|
76
|
+
return flipSvg(draw(en, o, back), { yaw: degrees(o?.yaw), pitch: degrees(o?.pitch), line: o?.style === "line", back: draw(en, o, !back), thick: 0.016 });
|
|
77
|
+
};
|
|
78
|
+
return { id, en, ja, source: "ours", kind: "flat", model: null, svg, frame };
|
|
79
|
+
}
|
|
@@ -23,6 +23,10 @@ import { TROPHY_PLAQUE } from "./trophy/plaque.js";
|
|
|
23
23
|
import { TROPHY_BELT } from "./trophy/belt.js";
|
|
24
24
|
import { TROPHY_LAUREL } from "./trophy/laurel.js";
|
|
25
25
|
import { TROPHY_ROSETTE } from "./trophy/rosette.js";
|
|
26
|
+
import { TROPHY_SUMO_CUP } from "./trophy/sumo-cup.js";
|
|
27
|
+
import { TROPHY_CHAMPIONSHIP_BANNER } from "./trophy/championship-banner.js";
|
|
28
|
+
import { TROPHY_RING } from "./trophy/ring.js";
|
|
29
|
+
import { TROPHY_BANNER } from "./trophy/banner.js";
|
|
26
30
|
import { MEDAL_GOLD } from "./medal/gold.js";
|
|
27
31
|
import { MEDAL_SILVER } from "./medal/silver.js";
|
|
28
32
|
import { MEDAL_BRONZE } from "./medal/bronze.js";
|
|
@@ -33,6 +37,7 @@ import { CREST_SHIELD } from "./crest/shield.js";
|
|
|
33
37
|
import { CREST_ROUNDEL } from "./crest/roundel.js";
|
|
34
38
|
import { CREST_DIAMOND } from "./crest/diamond.js";
|
|
35
39
|
import { CREST_PENNANT } from "./crest/pennant.js";
|
|
40
|
+
import { PENNANT_CHAMPIONSHIP } from "./pennant/championship.js";
|
|
36
41
|
import { BALLS } from "./ball/ball.index.js";
|
|
37
42
|
import { GEARS } from "./gear/gear.index.js";
|
|
38
43
|
import { SPORT_NFL } from "./sport/nfl.js";
|
|
@@ -68,6 +73,11 @@ import { SPORT_FIELDHOCKEY } from "./sport/fieldhockey.js";
|
|
|
68
73
|
import { SPORT_HANDBALL } from "./sport/handball.js";
|
|
69
74
|
import { SPORT_ATHLETICS } from "./sport/athletics.js";
|
|
70
75
|
import { SPORT_SKATING } from "./sport/skating.js";
|
|
76
|
+
import { SPORT_SUMO } from "./sport/sumo.js";
|
|
77
|
+
import { SPORT_KENDO } from "./sport/kendo.js";
|
|
78
|
+
import { SPORT_JUDO } from "./sport/judo.js";
|
|
79
|
+
import { SPORT_KYUDO } from "./sport/kyudo.js";
|
|
80
|
+
import { SPORT_KARATE } from "./sport/karate.js";
|
|
71
81
|
/** The balls and the gear are drawn by families, each family in a module of its own, and listed in order by their own indexes. */
|
|
72
82
|
const PIECES = Object.fromEntries([...BALLS, ...GEARS].map((piece) => [piece.id, piece]));
|
|
73
83
|
/** The emblems drawn one to a module: every id but the balls and the gear. A missing or extra entry is a type error. */
|
|
@@ -97,6 +107,10 @@ const OWN = {
|
|
|
97
107
|
"trophy:belt": TROPHY_BELT,
|
|
98
108
|
"trophy:laurel": TROPHY_LAUREL,
|
|
99
109
|
"trophy:rosette": TROPHY_ROSETTE,
|
|
110
|
+
"trophy:sumo-cup": TROPHY_SUMO_CUP,
|
|
111
|
+
"trophy:championship-banner": TROPHY_CHAMPIONSHIP_BANNER,
|
|
112
|
+
"trophy:ring": TROPHY_RING,
|
|
113
|
+
"trophy:banner": TROPHY_BANNER,
|
|
100
114
|
"medal:gold": MEDAL_GOLD,
|
|
101
115
|
"medal:silver": MEDAL_SILVER,
|
|
102
116
|
"medal:bronze": MEDAL_BRONZE,
|
|
@@ -107,6 +121,7 @@ const OWN = {
|
|
|
107
121
|
"crest:roundel": CREST_ROUNDEL,
|
|
108
122
|
"crest:diamond": CREST_DIAMOND,
|
|
109
123
|
"crest:pennant": CREST_PENNANT,
|
|
124
|
+
"pennant:championship": PENNANT_CHAMPIONSHIP,
|
|
110
125
|
"sport:nfl": SPORT_NFL,
|
|
111
126
|
"sport:cfl": SPORT_CFL,
|
|
112
127
|
"sport:ifaf": SPORT_IFAF,
|
|
@@ -140,6 +155,11 @@ const OWN = {
|
|
|
140
155
|
"sport:handball": SPORT_HANDBALL,
|
|
141
156
|
"sport:athletics": SPORT_ATHLETICS,
|
|
142
157
|
"sport:skating": SPORT_SKATING,
|
|
158
|
+
"sport:sumo": SPORT_SUMO,
|
|
159
|
+
"sport:kendo": SPORT_KENDO,
|
|
160
|
+
"sport:judo": SPORT_JUDO,
|
|
161
|
+
"sport:kyudo": SPORT_KYUDO,
|
|
162
|
+
"sport:karate": SPORT_KARATE,
|
|
143
163
|
};
|
|
144
164
|
/** Every emblem, by id. */
|
|
145
165
|
export const EMBLEM_TABLE = { ...OWN, ...PIECES };
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { type Emblem, type EmblemSource, type Metal } from "./emblem.core.ts";
|
|
2
|
+
import { type Plate } from "./emblem.engrave.ts";
|
|
2
3
|
/**
|
|
3
4
|
* What a shape is for. `m` the main metal, shaded across a sheet; `o` the main metal on a round, lit from a corner; `e` a bevelled ring (a line the width of the third item, bright on one side and dark on
|
|
4
5
|
* the other); `s` its shade; `l` a glint (a fill with no outline, left out of the line style); `r` the second colour (a ribbon, a band: the `accent` option); `f` a fixed colour (the third item); `p` white
|
|
5
6
|
* ink at a tenth; `k` ink; `n` an outline with no fill; `t` a thin line (a detail); `b` a bold line in ink (a limb, a shaft); `d` a bold line in a fixed dark colour (a numeral on a medal).
|
|
6
7
|
*/
|
|
7
|
-
export type Role = "m" | "o" | "e" | "s" | "l" | "r" | "f" | "p" | "k" | "n" | "t" | "b" | "d";
|
|
8
|
+
export type Role = DropRole | "m" | "o" | "e" | "s" | "l" | "r" | "f" | "p" | "k" | "n" | "t" | "b" | "d";
|
|
9
|
+
/** A role in capitals is the same shape, left out of a face that is engraved (the star of a medal, the numeral of a place badge, the lines of a plaque). */
|
|
10
|
+
type DropRole = "M" | "D" | "T" | "E";
|
|
8
11
|
/** A path, its role, and for `f` its colour and for `e` its width. */
|
|
9
12
|
export type Shape = readonly [Role, string, string?];
|
|
10
13
|
/** A flat object or an icon. */
|
|
@@ -25,12 +28,19 @@ export interface FlatModel {
|
|
|
25
28
|
ink?: boolean;
|
|
26
29
|
/** Shapes drawn when there are no initials, for a crest (and only then). */
|
|
27
30
|
blank?: readonly Shape[];
|
|
31
|
+
/** Where an engraving goes: the plate of the front (and of the year when a plate of its own holds it) and of the back, and the rank the front takes when it is engraved and none is given (a place badge's own place). */
|
|
32
|
+
engrave?: {
|
|
33
|
+
front: Plate;
|
|
34
|
+
year?: Plate;
|
|
35
|
+
back?: Plate;
|
|
36
|
+
rank?: string;
|
|
37
|
+
};
|
|
28
38
|
/** Where a crest's letters go: the centre's x and y, and the size for one letter. */
|
|
29
39
|
letters?: readonly [x: number, y: number, size: number];
|
|
30
40
|
}
|
|
31
41
|
/** A flat object (a medal, a crest, a plaque): drawn as it is by `svg`; turned about its vertical axis it flips like a coin and shows its back, with its rim for a moment edge on. */
|
|
32
42
|
export declare function defineFlat(id: string, en: string, ja: string, source: EmblemSource, model: FlatModel): Emblem<FlatModel>;
|
|
33
|
-
/** An icon: one drawing from shapes,
|
|
43
|
+
/** An icon: one drawing from shapes. Turned, it is a thin cut-out: it squeezes like a coin, shows its front mirrored and a little darker past a quarter turn, and has a dark rim. */
|
|
34
44
|
export declare function defineIcon(id: string, en: string, ja: string, source: EmblemSource, model: FlatModel): Emblem<FlatModel>;
|
|
35
45
|
/** A circle as a path, so every shape is one kind of thing. */
|
|
36
46
|
export declare const circlePath: (cx: number, cy: number, r: number) => string;
|
|
@@ -38,3 +48,10 @@ export declare const circlePath: (cx: number, cy: number, r: number) => string;
|
|
|
38
48
|
export declare const ellipsePath: (cx: number, cy: number, rx: number, ry: number) => string;
|
|
39
49
|
/** A star as a path, one point up. */
|
|
40
50
|
export declare function starPath(cx: number, cy: number, outer: number, inner: number, points?: number): string;
|
|
51
|
+
/** A regular polygon of `n` sides with a corner at `deg` degrees (0 is straight up), as a path. */
|
|
52
|
+
export declare function polygon(cx: number, cy: number, r: number, n: number, deg?: number): string;
|
|
53
|
+
/** An ellipse turned `deg` degrees about its centre, as a path. */
|
|
54
|
+
export declare function tiltedEllipse(cx: number, cy: number, rx: number, ry: number, deg: number): string;
|
|
55
|
+
/** A bar with square ends from one point to another, `w` wide, as a path. */
|
|
56
|
+
export declare function bar(x1: number, y1: number, x2: number, y2: number, w: number): string;
|
|
57
|
+
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// A flat object or an icon: a list of shapes, each with a role (the metal, its shade, a glint, a second colour, a fixed colour, ink, a line), drawn with gradients in the metal style, in flat fills in the
|
|
2
2
|
// `color` style and as one outline in the `line` style. A flat object (a medal, a crest, a plaque) also has a back face and a rim, so the turning view flips it like a coin; an icon (a sport's pictogram)
|
|
3
3
|
// is drawn as it is. Imports only the core.
|
|
4
|
-
import { bevelGradient, defsOf, degrees, escape, hexOf, mainOf, mix, n2, onColour, orbGradient, readColour, resolve, sheetGradient, wrap, } from "./emblem.core.js";
|
|
4
|
+
import { bevelGradient, defsOf, degrees, escape, hexOf, labelled, mainOf, mix, n2, onColour, orbGradient, readColour, resolve, sheetGradient, wrap, } from "./emblem.core.js";
|
|
5
|
+
import { engravingOf, linesOf, plateMarkup } from "./emblem.engrave.js";
|
|
6
|
+
import { flipSvg } from "./emblem.flip.js";
|
|
5
7
|
const use = (ctx, g) => {
|
|
6
8
|
ctx.used.push(g);
|
|
7
9
|
return g.fill;
|
|
@@ -55,14 +57,28 @@ function letters(model, ctx) {
|
|
|
55
57
|
const fs = n === 1 ? size : n === 2 ? size * 0.82 : size * 0.64;
|
|
56
58
|
return `<text x="${x}" y="${n2(y + fs * 0.36)}" font-size="${n2(fs)}" font-weight="800" font-family="system-ui,sans-serif" text-anchor="middle" fill="${ctx.style === "line" ? "currentColor" : onColour(ctx.main)}" stroke="none">${escape(ctx.initials)}</text>`;
|
|
57
59
|
}
|
|
58
|
-
/**
|
|
59
|
-
function
|
|
60
|
+
/** The lines cut into the face on `side`, as markup, or an empty string when there is nothing to cut or nowhere to cut it. */
|
|
61
|
+
function engraved(model, ctx, side) {
|
|
62
|
+
const e = ctx.engrave;
|
|
63
|
+
const plates = model.engrave;
|
|
64
|
+
if (plates === undefined)
|
|
65
|
+
return "";
|
|
66
|
+
if (side === "back")
|
|
67
|
+
return plates.back === undefined || e.back === undefined ? "" : plateMarkup(linesOf({ free: e.back }).main, plates.back, ctx.main, ctx.style);
|
|
68
|
+
if (e.free === undefined && e.label === undefined && e.year === undefined && e.rank === undefined)
|
|
69
|
+
return "";
|
|
70
|
+
const { main, year } = linesOf(e, { rank: plates.rank, yearApart: plates.year !== undefined });
|
|
71
|
+
return plateMarkup(main, plates.front, ctx.main, ctx.style) + (year === undefined || plates.year === undefined ? "" : plateMarkup([[{ text: year, weight: 1 }]], plates.year, ctx.main, ctx.style));
|
|
72
|
+
}
|
|
73
|
+
/** One face of an object as markup: its shapes (the ones an engraving replaces left out when there is one), then the engraving, and a crest's letters. */
|
|
74
|
+
function face(model, shapes, ctx, text = "") {
|
|
75
|
+
const shown = shapes.filter((x) => text === "" || x[0] === x[0].toLowerCase()).map((x) => (x[0] === x[0].toLowerCase() ? x : [x[0].toLowerCase(), x[1], x[2]]));
|
|
60
76
|
const blank = ctx.initials === "" && model.blank !== undefined ? shapesMarkup(model.blank, ctx) : "";
|
|
61
|
-
return shapesMarkup(
|
|
77
|
+
return shapesMarkup(shown, ctx) + blank + letters(model, ctx) + text;
|
|
62
78
|
}
|
|
63
79
|
function context(model, r) {
|
|
64
80
|
const main = r.tint ?? (model.color === undefined ? mainOf(model.metal, undefined) : readColour(model.color));
|
|
65
|
-
return { main, style: r.style, sheet: sheetGradient(main), orb: orbGradient(main), bevel: bevelGradient(main), ribbon: r.accent ?? model.ribbon ?? "#c0392b", initials: r.initials, used: [] };
|
|
81
|
+
return { main, style: r.style, sheet: sheetGradient(main), orb: orbGradient(main), bevel: bevelGradient(main), ribbon: r.accent ?? model.ribbon ?? "#c0392b", initials: r.initials, engrave: {}, used: [] };
|
|
66
82
|
}
|
|
67
83
|
function document(title, model, r, ctx, body) {
|
|
68
84
|
return wrap({ title, size: r.size, style: r.style, theme: r.theme, defs: r.style === "metal" ? defsOf(ctx.used) : "", body, box: model.box, edge: model.ink === true ? undefined : mix(ctx.main, -0.5), width: model.ink === true ? undefined : 0.8 });
|
|
@@ -72,28 +88,31 @@ export function defineFlat(id, en, ja, source, model) {
|
|
|
72
88
|
const draw = (o, yaw, pitch) => {
|
|
73
89
|
const r = resolve(o);
|
|
74
90
|
const ctx = context(model, r);
|
|
91
|
+
ctx.engrave = engravingOf(o);
|
|
75
92
|
if (yaw === 0 && pitch === 0)
|
|
76
|
-
return document(en, model, r, ctx, face(model, model.front, ctx));
|
|
93
|
+
return document(en, model, r, ctx, face(model, model.front, ctx, engraved(model, ctx, "front")));
|
|
77
94
|
const across = Math.cos((yaw * Math.PI) / 180);
|
|
78
95
|
const tilt = Math.cos((pitch * Math.PI) / 180);
|
|
79
96
|
const shown = across >= 0 ? model.front : (model.back ?? model.front.filter((x) => x[0] === "m" || x[0] === "o" || x[0] === "s"));
|
|
80
97
|
const sx = Math.max(Math.abs(across), 0.02);
|
|
81
98
|
const sy = Math.max(Math.abs(tilt), 0.02);
|
|
82
|
-
|
|
99
|
+
// The rim is on the left for a positive yaw, which carries the front toward the right (as it does for a ball, a cup and a drag to the right).
|
|
100
|
+
const rim = -(model.rim ?? 0) * Math.sin((yaw * Math.PI) / 180);
|
|
83
101
|
const [x0, y0, w, h] = (model.box ?? "0 0 64 64").split(" ").map(Number);
|
|
84
102
|
const thick = rim === 0 ? "" : `<g transform="translate(${n2(rim)} 0)">${shapesMarkup(model.front.filter((x) => x[0] === "m" || x[0] === "o").map((x) => ["s", x[1]]), ctx)}</g>`;
|
|
85
|
-
return document(en, model, r, ctx, `<g transform="translate(${n2(x0 + w / 2)} ${n2(y0 + h / 2)}) scale(${n2(sx)} ${n2(sy)}) translate(${n2(-x0 - w / 2)} ${n2(-y0 - h / 2)})">${thick}${face(model, shown, ctx)}</g>`);
|
|
103
|
+
return document(en, model, r, ctx, `<g transform="translate(${n2(x0 + w / 2)} ${n2(y0 + h / 2)}) scale(${n2(sx)} ${n2(sy)}) translate(${n2(-x0 - w / 2)} ${n2(-y0 - h / 2)})">${thick}${face(model, shown, ctx, engraved(model, ctx, across >= 0 ? "front" : "back"))}</g>`);
|
|
86
104
|
};
|
|
87
|
-
return { id, en, ja, source, kind: "flat", model, svg: (o) => draw(o, 0, 0), frame: (o) => draw(o, degrees(o?.yaw), degrees(o?.pitch)) };
|
|
105
|
+
return { id, en, ja, source, kind: "flat", model, svg: labelled(en, ja, (o) => draw(o, 0, 0)), frame: labelled(en, ja, (o) => draw(o, degrees(o?.yaw), degrees(o?.pitch))) };
|
|
88
106
|
}
|
|
89
|
-
/** An icon: one drawing from shapes,
|
|
107
|
+
/** An icon: one drawing from shapes. Turned, it is a thin cut-out: it squeezes like a coin, shows its front mirrored and a little darker past a quarter turn, and has a dark rim. */
|
|
90
108
|
export function defineIcon(id, en, ja, source, model) {
|
|
91
|
-
const svg = (o) => {
|
|
109
|
+
const svg = labelled(en, ja, (o) => {
|
|
92
110
|
const r = resolve(o);
|
|
93
111
|
const ctx = context(model, r);
|
|
94
112
|
return document(en, model, r, ctx, face(model, model.front, ctx));
|
|
95
|
-
};
|
|
96
|
-
|
|
113
|
+
});
|
|
114
|
+
const frame = (o) => flipSvg(svg(o), { yaw: degrees(o?.yaw), pitch: degrees(o?.pitch), line: o?.style === "line", thick: 0.03 });
|
|
115
|
+
return { id, en, ja, source, kind: "icon", model, svg, frame: labelled(en, ja, frame) };
|
|
97
116
|
}
|
|
98
117
|
/** A circle as a path, so every shape is one kind of thing. */
|
|
99
118
|
export const circlePath = (cx, cy, r) => `M${n2(cx - r)} ${n2(cy)}a${n2(r)} ${n2(r)} 0 1 0 ${n2(2 * r)} 0a${n2(r)} ${n2(r)} 0 1 0 ${n2(-2 * r)} 0Z`;
|
|
@@ -109,3 +128,26 @@ export function starPath(cx, cy, outer, inner, points = 5) {
|
|
|
109
128
|
}
|
|
110
129
|
return `${parts.join("")}Z`;
|
|
111
130
|
}
|
|
131
|
+
/** A regular polygon of `n` sides with a corner at `deg` degrees (0 is straight up), as a path. */
|
|
132
|
+
export function polygon(cx, cy, r, n, deg = 0) {
|
|
133
|
+
let d = "";
|
|
134
|
+
for (let i = 0; i < n; i++) {
|
|
135
|
+
const a = ((deg + (360 * i) / n - 90) * Math.PI) / 180;
|
|
136
|
+
d += `${i === 0 ? "M" : "L"}${n2(cx + r * Math.cos(a))} ${n2(cy + r * Math.sin(a))}`;
|
|
137
|
+
}
|
|
138
|
+
return `${d}Z`;
|
|
139
|
+
}
|
|
140
|
+
/** An ellipse turned `deg` degrees about its centre, as a path. */
|
|
141
|
+
export function tiltedEllipse(cx, cy, rx, ry, deg) {
|
|
142
|
+
const a = (deg * Math.PI) / 180;
|
|
143
|
+
const x = rx * Math.cos(a);
|
|
144
|
+
const y = rx * Math.sin(a);
|
|
145
|
+
return `M${n2(cx - x)} ${n2(cy - y)}A${n2(rx)} ${n2(ry)} ${deg} 1 1 ${n2(cx + x)} ${n2(cy + y)}A${n2(rx)} ${n2(ry)} ${deg} 1 1 ${n2(cx - x)} ${n2(cy - y)}Z`;
|
|
146
|
+
}
|
|
147
|
+
/** A bar with square ends from one point to another, `w` wide, as a path. */
|
|
148
|
+
export function bar(x1, y1, x2, y2, w) {
|
|
149
|
+
const l = Math.hypot(x2 - x1, y2 - y1) || 1;
|
|
150
|
+
const nx = (-(y2 - y1) / l) * (w / 2);
|
|
151
|
+
const ny = ((x2 - x1) / l) * (w / 2);
|
|
152
|
+
return `M${n2(x1 + nx)} ${n2(y1 + ny)}L${n2(x2 + nx)} ${n2(y2 + ny)}L${n2(x2 - nx)} ${n2(y2 - ny)}L${n2(x1 - nx)} ${n2(y1 - ny)}Z`;
|
|
153
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type EmblemLocale, type EmblemOptions } from "./emblem.core.ts";
|
|
2
|
+
import { type EmblemRef } from "./emblem.api.ts";
|
|
3
|
+
/** What `emblemSprite` takes: the options every symbol is drawn with, and the prefix of its ids. */
|
|
4
|
+
export interface SpriteOptions extends EmblemOptions {
|
|
5
|
+
/** The start of every id in the sheet (letters, digits and hyphens, starting with a letter). By default it is made from the emblems and the options, so the same call gives the same sheet. */
|
|
6
|
+
prefix?: string;
|
|
7
|
+
}
|
|
8
|
+
/** What one `<use>` takes. */
|
|
9
|
+
export interface UseOptions {
|
|
10
|
+
/** The width and height in pixels (64 by default). */
|
|
11
|
+
size?: number;
|
|
12
|
+
/** A class for the `<svg>` that holds the `<use>`. */
|
|
13
|
+
class?: string;
|
|
14
|
+
/** The `<title>`: a string writes that title, `false` drops it and hides the drawing from a screen reader. By default it is the emblem's name. */
|
|
15
|
+
title?: string | false;
|
|
16
|
+
/** The language of the default title, `"en"` or `"ja"`. */
|
|
17
|
+
locale?: EmblemLocale;
|
|
18
|
+
}
|
|
19
|
+
/** A sheet of symbols and the snippets that draw them. */
|
|
20
|
+
export interface Sprite {
|
|
21
|
+
/** One hidden `<svg>` of `<symbol>`s; put it once in the page, anywhere in `<body>`. */
|
|
22
|
+
sheet: string;
|
|
23
|
+
/** The markup for one use of a symbol: `<svg><title>…</title><use href="#…"/></svg>`. The emblem must be one the sprite was made with. */
|
|
24
|
+
use: (ref: EmblemRef, options?: UseOptions) => string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A sprite sheet: every emblem in `ids` drawn once as a `<symbol>`, with the gradients and other definitions shared and every id unique across the sheet, and a `use(id, { size, class, title })` that returns the
|
|
28
|
+
* markup for one more of it. Put `sheet` in the page once; a bracket or a table that shows the same ball or medal fifty times then adds a few nodes each time, not the whole drawing. The symbols are drawn without the
|
|
29
|
+
* grain a large ball has by default (pass `grain: true` to keep it). `style`, `tint`, `accent` and `theme` are the sheet's, for every symbol; a different look is another sprite, or an id that names its style (`"medal:gold/line"`).
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { emblemSprite } from "@johnmorrisdotca/dougu-sports/emblems";
|
|
33
|
+
* const sprite = emblemSprite(["ball:soccer", "medal:gold"]);
|
|
34
|
+
* console.log(sprite.use("ball:soccer", { size: 24 }).startsWith("<svg"));
|
|
35
|
+
* // true
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function emblemSprite(ids: readonly EmblemRef[], options?: SpriteOptions): Sprite;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// A sheet of symbols: each emblem drawn once, then used as many times as a page wants it with a `<use>` that costs a few nodes and a few dozen bytes.
|
|
2
|
+
import { EMBLEM_INK, escape, resolve } from "./emblem.core.js";
|
|
3
|
+
import { emblemOf, parseEmblem, renderEmblem } from "./emblem.api.js";
|
|
4
|
+
import { IdSpace, partsOf } from "./emblem.compose.js";
|
|
5
|
+
const hash = (text) => {
|
|
6
|
+
let h = 2166136261;
|
|
7
|
+
for (let i = 0; i < text.length; i++)
|
|
8
|
+
h = Math.imul(h ^ text.charCodeAt(i), 16777619);
|
|
9
|
+
return (h >>> 0).toString(36);
|
|
10
|
+
};
|
|
11
|
+
/** The key a reference goes by in a sprite: the id, and the style when the reference names one. */
|
|
12
|
+
function keyOf(ref) {
|
|
13
|
+
if (typeof ref !== "string")
|
|
14
|
+
return ref.id;
|
|
15
|
+
const parsed = parseEmblem(ref);
|
|
16
|
+
if (parsed === undefined)
|
|
17
|
+
throw new Error(`Unknown emblem "${ref.slice(0, 60)}". Ids look like "ball:soccer" or "medal:gold/line"; see EMBLEM_IDS.`);
|
|
18
|
+
return parsed.style === undefined ? parsed.id : `${parsed.id}/${parsed.style}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A sprite sheet: every emblem in `ids` drawn once as a `<symbol>`, with the gradients and other definitions shared and every id unique across the sheet, and a `use(id, { size, class, title })` that returns the
|
|
22
|
+
* markup for one more of it. Put `sheet` in the page once; a bracket or a table that shows the same ball or medal fifty times then adds a few nodes each time, not the whole drawing. The symbols are drawn without the
|
|
23
|
+
* grain a large ball has by default (pass `grain: true` to keep it). `style`, `tint`, `accent` and `theme` are the sheet's, for every symbol; a different look is another sprite, or an id that names its style (`"medal:gold/line"`).
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { emblemSprite } from "@johnmorrisdotca/dougu-sports/emblems";
|
|
27
|
+
* const sprite = emblemSprite(["ball:soccer", "medal:gold"]);
|
|
28
|
+
* console.log(sprite.use("ball:soccer", { size: 24 }).startsWith("<svg"));
|
|
29
|
+
* // true
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function emblemSprite(ids, options) {
|
|
33
|
+
const keys = [...new Set(ids.map(keyOf))];
|
|
34
|
+
const { prefix: asked, ...drawn } = options ?? {};
|
|
35
|
+
const prefix = typeof asked === "string" && /^[A-Za-z][\w-]{0,31}$/.test(asked) ? asked : `dgs${hash(`${keys.join(",")}|${JSON.stringify(drawn)}`)}`;
|
|
36
|
+
const theme = drawn.theme === "dark" || drawn.theme === "light" || drawn.theme === "auto" ? drawn.theme : "inherit";
|
|
37
|
+
const space = new IdSpace(prefix);
|
|
38
|
+
const symbols = [];
|
|
39
|
+
const symbolOf = new Map();
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
const svg = renderEmblem(key, { ...drawn, theme: "inherit", title: false, locale: undefined, grain: drawn.grain ?? false });
|
|
42
|
+
const parts = partsOf(svg);
|
|
43
|
+
const name = `${prefix}-${key.replace(/[^a-z0-9]+/gi, "-").toLowerCase()}`;
|
|
44
|
+
symbolOf.set(key, name);
|
|
45
|
+
symbols.push(`<symbol id="${name}" viewBox="${escape(parts.box)}">${space.add(parts)}</symbol>`);
|
|
46
|
+
}
|
|
47
|
+
const rule = theme === "auto" ? `<style>@media(prefers-color-scheme:dark){svg.${prefix}{color:${EMBLEM_INK.dark}}}</style>` : "";
|
|
48
|
+
const sheet = `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" style="position:absolute;width:0;height:0;overflow:hidden" aria-hidden="true" focusable="false">` +
|
|
49
|
+
`${rule}${space.defs.length === 0 ? "" : `<defs>${space.defs.join("")}</defs>`}${symbols.join("")}</svg>`;
|
|
50
|
+
const color = theme === "dark" ? EMBLEM_INK.dark : theme === "light" ? EMBLEM_INK.light : "";
|
|
51
|
+
const use = (ref, o) => {
|
|
52
|
+
const key = keyOf(ref);
|
|
53
|
+
const symbol = symbolOf.get(key);
|
|
54
|
+
if (symbol === undefined)
|
|
55
|
+
throw new Error(`"${key}" is not in this sprite; it holds ${keys.slice(0, 8).join(", ")}${keys.length > 8 ? ", …" : ""}.`);
|
|
56
|
+
const size = resolve({ size: o?.size }).size;
|
|
57
|
+
const classes = [theme === "auto" ? prefix : "", typeof o?.class === "string" ? o.class.trim() : ""].filter(Boolean).join(" ");
|
|
58
|
+
const { emblem } = emblemOf(ref);
|
|
59
|
+
const title = o?.title === false ? false : typeof o?.title === "string" && o.title.trim() !== "" ? o.title.trim().slice(0, 300) : o?.locale === "ja" ? emblem.ja : emblem.en;
|
|
60
|
+
const lang = o?.locale === "ja" && typeof o?.title !== "string" ? ' lang="ja"' : "";
|
|
61
|
+
return (`<svg width="${size}" height="${size}"${title === false ? ' aria-hidden="true"' : ` role="img"${lang}`}${classes === "" ? "" : ` class="${escape(classes)}"`}${color === "" ? "" : ` color="${color}"`}>` +
|
|
62
|
+
`${title === false ? "" : `<title>${escape(title)}</title>`}<use href="#${symbol}"/></svg>`);
|
|
63
|
+
};
|
|
64
|
+
return { sheet, use };
|
|
65
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type EmblemOptions } from "./emblem.core.ts";
|
|
2
|
+
import { type EmblemRef } from "./emblem.api.ts";
|
|
3
|
+
/** What `emblemDataUri` takes: the emblem's options, and the colour of the line. */
|
|
4
|
+
export interface DataUriOptions extends EmblemOptions {
|
|
5
|
+
/** The colour of the outline and of anything drawn in the ink: `#rgb`, `#rrggbb` or `rgb()`. It wins over `theme`. Anything else is ignored. */
|
|
6
|
+
color?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Text as the body of a data address: every character that could end an attribute, a quoted `url()` or an unquoted one is percent-encoded, and the rest is left alone. */
|
|
9
|
+
export declare function encodeSvg(svg: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* An emblem as a data address, `data:image/svg+xml;utf8,…`, with every character that could break an attribute or a CSS `url()` percent-encoded, so the string goes anywhere: `img.src`, `background-image:
|
|
12
|
+
* url("…")`, `new Image().src` for a canvas. The ink is settled for you, because `currentColor` does not cross an `<img>` boundary: `color` is the line's colour; without it `theme: "dark"` is pale ink, and `"light"`
|
|
13
|
+
* and `"inherit"` (the default) are dark ink. `theme: "auto"` keeps the page's colour scheme (`prefers-color-scheme`) in the image.
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { emblemDataUri } from "@johnmorrisdotca/dougu-sports/emblems";
|
|
17
|
+
* const src = emblemDataUri("medal:gold", { size: 48 });
|
|
18
|
+
* console.log(src.startsWith("data:image/svg+xml;utf8,%3Csvg"));
|
|
19
|
+
* // true
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function emblemDataUri(ref: EmblemRef, options?: DataUriOptions): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// An emblem as a data address, for an `<img>`, a CSS background or a canvas. An image cannot inherit its page's text colour, so the colour is settled here: the line style, a pictogram and a Material
|
|
2
|
+
// icon are drawn in `currentColor`, and that becomes a concrete colour.
|
|
3
|
+
import { EMBLEM_INK, hexOf, readColour } from "./emblem.core.js";
|
|
4
|
+
import { renderEmblem } from "./emblem.api.js";
|
|
5
|
+
/** Text as the body of a data address: every character that could end an attribute, a quoted `url()` or an unquoted one is percent-encoded, and the rest is left alone. */
|
|
6
|
+
export function encodeSvg(svg) {
|
|
7
|
+
return encodeURIComponent(svg).replace(/%(3A|2F|2C|3B|3D|40)/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/['()*!]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* An emblem as a data address, `data:image/svg+xml;utf8,…`, with every character that could break an attribute or a CSS `url()` percent-encoded, so the string goes anywhere: `img.src`, `background-image:
|
|
11
|
+
* url("…")`, `new Image().src` for a canvas. The ink is settled for you, because `currentColor` does not cross an `<img>` boundary: `color` is the line's colour; without it `theme: "dark"` is pale ink, and `"light"`
|
|
12
|
+
* and `"inherit"` (the default) are dark ink. `theme: "auto"` keeps the page's colour scheme (`prefers-color-scheme`) in the image.
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { emblemDataUri } from "@johnmorrisdotca/dougu-sports/emblems";
|
|
16
|
+
* const src = emblemDataUri("medal:gold", { size: 48 });
|
|
17
|
+
* console.log(src.startsWith("data:image/svg+xml;utf8,%3Csvg"));
|
|
18
|
+
* // true
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function emblemDataUri(ref, options) {
|
|
22
|
+
const { color, ...rest } = options ?? {};
|
|
23
|
+
const picked = readColour(color);
|
|
24
|
+
const ink = picked !== undefined ? hexOf(picked) : rest.theme === "dark" ? EMBLEM_INK.dark : rest.theme === "auto" ? undefined : EMBLEM_INK.light;
|
|
25
|
+
let svg = renderEmblem(ref, { ...rest, theme: ink === undefined ? "auto" : "inherit" });
|
|
26
|
+
// `currentColor` becomes the ink. A drawing that asked for `auto` keeps its colour-scheme rule, with the dark ink as its light default.
|
|
27
|
+
svg = ink === undefined ? svg.replace("<style>@media", `<style>svg{color:${EMBLEM_INK.light}}@media`) : svg.replace(/currentColor/g, ink);
|
|
28
|
+
return `data:image/svg+xml;utf8,${encodeSvg(svg)}`;
|
|
29
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const GEAR_BADMINTON_RACKET: import("./gear.index.ts").Piece<import("./gear.core.ts").
|
|
1
|
+
export declare const GEAR_BADMINTON_RACKET: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
// A badminton racket and a shuttlecock, drawn for this package: an oval strung head on a slender frame, a T-shaped throat and a grip, and a feathered shuttlecock on its cork.
|
|
2
|
+
import { capsule, drawn, ellipse, seg, strings } from "./gear.core.js";
|
|
3
|
+
const T = "rotate(34 32 32)";
|
|
4
|
+
const S = "translate(50.5 50) rotate(48) scale(.95) translate(-32 -13)";
|
|
5
|
+
export const GEAR_BADMINTON_RACKET = /* @__PURE__ */ drawn("gear:badminton-racket", "Badminton racket", "バドミントンのラケット", {
|
|
6
|
+
main: "#2f8fe0",
|
|
7
|
+
accent: "#2a2f38",
|
|
8
|
+
parts: [
|
|
9
|
+
{ d: strings(32, 15, 10.4, 13.4, 3.1), fill: "none", stroke: "#9aa7b6", w: 0.6, t: T },
|
|
10
|
+
{ d: ellipse(32, 15, 11, 14), fill: "none", stroke: "$main", w: 2.6, t: T },
|
|
11
|
+
{ d: "M24.2 24.6L30.6 36M39.8 24.6L33.4 36", fill: "none", stroke: "$main", w: 2.2, t: T },
|
|
12
|
+
{ d: capsule(32, 34, 32, 50, 2.2), fill: "#aeb6c0", t: T, cyl: 0.9, shade: 0 },
|
|
13
|
+
{ d: capsule(32, 46, 32, 62, 4.4), fill: "$accent", t: T, cyl: 0.8, shade: 0 },
|
|
14
|
+
{ d: seg(30, 52, 34, 52) + seg(30, 56, 34, 56), fill: "none", stroke: "#6a7482", w: 0.7, t: T, line: "none" },
|
|
15
|
+
{ d: "M28.4 20L24 4Q32 1 40 4L35.6 20z", fill: "#f4f6f8", stroke: "#9aa7b6", t: S, shade: 0.8 },
|
|
16
|
+
{ d: "M29.6 19L27 5M32 19.4V3.4M34.4 19L37 5", fill: "none", stroke: "#9aa7b6", w: 0.7, t: S, line: "none" },
|
|
17
|
+
{ d: "M27.6 20h8.8a4.4 4.4 0 0 1-8.8 0z", fill: "#e8cf9a", t: S, shade: 0.9 },
|
|
18
|
+
],
|
|
15
19
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const GEAR_BASEBALL_GLOVE: import("./gear.index.ts").Piece<import("./gear.core.ts").
|
|
1
|
+
export declare const GEAR_BASEBALL_GLOVE: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
// A baseball glove with a ball, drawn for this package: a leather mitt of four fingers and a thumb round a deep pocket, a laced web, a wrist strap and a ball in the pocket.
|
|
2
|
+
import { capsule, circle, drawn, ellipse } from "./gear.core.js";
|
|
3
|
+
export const GEAR_BASEBALL_GLOVE = /* @__PURE__ */ drawn("gear:baseball-glove", "Baseball glove", "野球のグラブ", {
|
|
4
|
+
main: "#b5702f",
|
|
5
|
+
accent: "#e6cfa0",
|
|
6
|
+
parts: [
|
|
7
|
+
{ d: capsule(17, 30, 12.6, 16, 9.6), fill: "$main", cyl: 0.8, shade: 0.3 },
|
|
8
|
+
{ d: capsule(28, 28, 26.6, 11.6, 10), fill: "$main", cyl: 0.8, shade: 0.3 },
|
|
9
|
+
{ d: capsule(39, 28.4, 40.6, 12.6, 9.8), fill: "$main", cyl: 0.8, shade: 0.3 },
|
|
10
|
+
{ d: capsule(48, 32, 52.6, 19.4, 9), fill: "$main", cyl: 0.8, shade: 0.3 },
|
|
11
|
+
{ d: capsule(12, 42, 5.8, 34.6, 10), fill: "$main", cyl: 0.8, shade: 0.3 },
|
|
12
|
+
{ d: "M9 34C7 47 16 59 32 59C48 59 58 49 56 34C53 25 45 23 36 23C27 23 13 23 9 34Z", fill: "$main", shade: 0.6 },
|
|
13
|
+
{ d: ellipse(32, 42, 15.4, 12.4), fill: "#8f5220", stroke: "#6a3a14", shade: 0, line: "stroke" },
|
|
14
|
+
{ d: "M13.4 24.2L17 29.4M23.4 20L24.6 26M34 20.6L34 26M45 22.6L42.4 27.4M8.6 36L13.4 36.6", fill: "none", stroke: "$accent", w: 1.4, line: "none" },
|
|
15
|
+
{ d: "M20 55.6Q32 61 45 55.4", fill: "none", stroke: "$accent", w: 1.6, line: "none" },
|
|
16
|
+
{ d: "M22 56.4L44 56.6L43.4 60.8Q32 63.4 23 60.8Z", fill: "#7a4318", stroke: "#4a280c", shade: 0.3, line: "none" },
|
|
17
|
+
{ d: circle(32, 41, 7.8), fill: "#f6f3ec", stroke: "#8f98a3", shade: 0.9 },
|
|
18
|
+
{ d: "M27.2 35.4Q31 41 27.2 46.6M36.8 35.4Q33 41 36.8 46.6", fill: "none", stroke: "#c4262e", w: 1, line: "none" },
|
|
19
|
+
{ d: "M8.6 35Q9.6 29 13 26.4", fill: "none", stroke: "#fff", w: 1.6, op: 0.4, line: "none", fx: true },
|
|
20
|
+
],
|
|
15
21
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PieceOptions } from "../ball/ball.core.ts";
|
|
2
|
+
/** The colours of a judo belt from the first rank to the black belt, in order, which `tint` takes. */
|
|
3
|
+
export declare const OBI_RANKS: {
|
|
4
|
+
readonly white: "#f2efe8";
|
|
5
|
+
readonly yellow: "#f2c21b";
|
|
6
|
+
readonly orange: "#ee8a1c";
|
|
7
|
+
readonly green: "#2f9a52";
|
|
8
|
+
readonly blue: "#2b64c8";
|
|
9
|
+
readonly brown: "#7a4a24";
|
|
10
|
+
readonly black: "#1f2228";
|
|
11
|
+
};
|
|
12
|
+
export type ObiRank = keyof typeof OBI_RANKS;
|
|
13
|
+
export declare const GEAR_JUDOGI: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
14
|
+
export declare const GEAR_KARATE_GI: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
15
|
+
export declare const GEAR_OBI: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
16
|
+
/** The belt of a rank, `"white"` to `"black"`: the same as `GEAR_OBI.svg({ ...options, tint: OBI_RANKS[rank] })`. An unknown rank is the black belt. */
|
|
17
|
+
export declare const obi: (rank: ObiRank, options?: PieceOptions) => string;
|