volleyballsimtypes 0.0.526 → 0.0.527

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.
Files changed (29) hide show
  1. package/dist/cjs/src/api/index.d.ts +1 -1
  2. package/dist/cjs/src/data/models/team-jersey.d.ts +3 -1
  3. package/dist/cjs/src/data/models/team-jersey.js +4 -0
  4. package/dist/cjs/src/data/transformers/team.js +2 -1
  5. package/dist/cjs/src/service/team/index.d.ts +1 -0
  6. package/dist/cjs/src/service/team/index.js +1 -0
  7. package/dist/cjs/src/service/team/jersey-defaults.d.ts +13 -0
  8. package/dist/cjs/src/service/team/jersey-defaults.js +79 -0
  9. package/dist/cjs/src/service/team/jersey.d.ts +29 -0
  10. package/dist/cjs/src/service/team/jersey.js +125 -1
  11. package/dist/cjs/src/service/team/schemas/team.z.d.ts +24 -0
  12. package/dist/cjs/src/service/team/schemas/team.z.js +2 -1
  13. package/dist/cjs/src/service/team/team.d.ts +2 -1
  14. package/dist/cjs/src/service/team/team.js +2 -1
  15. package/dist/esm/src/api/index.d.ts +1 -1
  16. package/dist/esm/src/data/models/team-jersey.d.ts +3 -1
  17. package/dist/esm/src/data/models/team-jersey.js +4 -0
  18. package/dist/esm/src/data/transformers/team.js +2 -1
  19. package/dist/esm/src/service/team/index.d.ts +1 -0
  20. package/dist/esm/src/service/team/index.js +1 -0
  21. package/dist/esm/src/service/team/jersey-defaults.d.ts +13 -0
  22. package/dist/esm/src/service/team/jersey-defaults.js +75 -0
  23. package/dist/esm/src/service/team/jersey.d.ts +29 -0
  24. package/dist/esm/src/service/team/jersey.js +120 -0
  25. package/dist/esm/src/service/team/schemas/team.z.d.ts +24 -0
  26. package/dist/esm/src/service/team/schemas/team.z.js +3 -2
  27. package/dist/esm/src/service/team/team.d.ts +2 -1
  28. package/dist/esm/src/service/team/team.js +2 -1
  29. package/package.json +1 -1
@@ -196,7 +196,7 @@ export type Team = Omit<DataProps<_Team>, 'roster' | '_rating' | 'rating' | 'tac
196
196
  nationalManager?: {
197
197
  displayName: string;
198
198
  teamId: string;
199
- teamShortName: string;
199
+ teamName: string;
200
200
  };
201
201
  };
202
202
  export type Standing = DataProps<_Standing> & {
@@ -1,10 +1,11 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model } from 'sequelize';
3
3
  import { TeamId, TeamModel } from '.';
4
- import { JerseyConfig } from '../../service';
4
+ import { JerseyConfig, AlternateJerseyConfig } from '../../service';
5
5
  export interface TeamJerseyAttributes {
6
6
  team_id: string;
7
7
  config: JerseyConfig;
8
+ alternate_config?: AlternateJerseyConfig | null;
8
9
  }
9
10
  export type TeamJerseyPk = 'team_id';
10
11
  export type TeamJerseyId = TeamJerseyModel[TeamJerseyPk];
@@ -12,6 +13,7 @@ export type TeamJerseyCreationAttributes = TeamJerseyAttributes;
12
13
  export declare class TeamJerseyModel extends Model<TeamJerseyAttributes, TeamJerseyCreationAttributes> implements TeamJerseyAttributes {
13
14
  team_id: string;
14
15
  config: JerseyConfig;
16
+ alternate_config?: AlternateJerseyConfig | null;
15
17
  team: TeamModel;
16
18
  getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
17
19
  setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
@@ -17,6 +17,10 @@ class TeamJerseyModel extends sequelize_1.Model {
17
17
  config: {
18
18
  type: sequelize_1.DataTypes.JSONB,
19
19
  allowNull: false
20
+ },
21
+ alternate_config: {
22
+ type: sequelize_1.DataTypes.JSONB,
23
+ allowNull: true
20
24
  }
21
25
  }, {
22
26
  sequelize,
@@ -53,6 +53,7 @@ function transformToObject(model, currentIteration, tacticsOverride) {
53
53
  divisionId: model.division_id,
54
54
  // Present only when the caller explicitly included the TeamJersey association (editor / effective-jersey
55
55
  // resolver); undefined on ordinary team loads, which heals to the id-derived default in the UI.
56
- jerseyConfig: model.TeamJersey?.config
56
+ jerseyConfig: model.TeamJersey?.config,
57
+ alternateJerseyConfig: model.TeamJersey?.alternate_config ?? undefined
57
58
  });
58
59
  }
@@ -1,5 +1,6 @@
1
1
  export * from './team';
2
2
  export * from './jersey';
3
+ export * from './jersey-defaults';
3
4
  export * from './team-generator';
4
5
  export * from './formation';
5
6
  export * from './tactics';
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./team"), exports);
18
18
  __exportStar(require("./jersey"), exports);
19
+ __exportStar(require("./jersey-defaults"), exports);
19
20
  __exportStar(require("./team-generator"), exports);
20
21
  __exportStar(require("./formation"), exports);
21
22
  __exportStar(require("./tactics"), exports);
@@ -0,0 +1,13 @@
1
+ import type { JerseyConfig, JerseyClothing, JerseyPattern, JerseyFont } from './jersey';
2
+ export interface JerseyPalette {
3
+ color1: string;
4
+ color2: string;
5
+ color3: string;
6
+ number1: string;
7
+ number2: string;
8
+ }
9
+ export declare const JERSEY_PALETTES: readonly JerseyPalette[];
10
+ export declare const JERSEY_PATTERN_ORDER: readonly JerseyPattern[];
11
+ export declare const JERSEY_CLOTHING_ORDER: readonly JerseyClothing[];
12
+ export declare const JERSEY_FONT_ORDER: readonly JerseyFont[];
13
+ export declare function jerseyFromTeamId(teamId: string): JerseyConfig;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JERSEY_FONT_ORDER = exports.JERSEY_CLOTHING_ORDER = exports.JERSEY_PATTERN_ORDER = exports.JERSEY_PALETTES = void 0;
4
+ exports.jerseyFromTeamId = jerseyFromTeamId;
5
+ // FNV-1a hash of a string, used to seed the PRNG below from a team id.
6
+ function hashSeed(str) {
7
+ let h = 2166136261;
8
+ for (let i = 0; i < str.length; i++) {
9
+ h ^= str.charCodeAt(i);
10
+ h = Math.imul(h, 16777619);
11
+ }
12
+ return h >>> 0;
13
+ }
14
+ // mulberry32: a tiny deterministic PRNG. Same seed -> same stream, so a team's kit is stable and reproducible.
15
+ function mulberry32(seed) {
16
+ let a = seed;
17
+ return () => {
18
+ a = (a + 0x6d2b79f5) | 0;
19
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
20
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
21
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
22
+ };
23
+ }
24
+ exports.JERSEY_PALETTES = [
25
+ { color1: '#606c38', color2: '#dda15e', color3: '#bc6c25', number1: '#fefae0', number2: '#283618' },
26
+ { color1: '#780000', color2: '#669bbc', color3: '#c1121f', number1: '#fdf0d5', number2: '#003049' },
27
+ { color1: '#2a9d8f', color2: '#f4a261', color3: '#e76f51', number1: '#264653', number2: '#e9c46a' },
28
+ { color1: '#219ebc', color2: '#fb8500', color3: '#8ecae6', number1: '#023047', number2: '#ffb703' },
29
+ { color1: '#1d3557', color2: '#e63946', color3: '#a8dadc', number1: '#f1faee', number2: '#457b9d' },
30
+ { color1: '#003566', color2: '#ffc300', color3: '#001d3d', number1: '#ffd60a', number2: '#000814' },
31
+ { color1: '#bc4749', color2: '#6a994e', color3: '#a7c957', number1: '#f2e8cf', number2: '#386641' },
32
+ { color1: '#6f1d1b', color2: '#99582a', color3: '#bb9457', number1: '#ffe6a7', number2: '#432818' },
33
+ { color1: '#ef476f', color2: '#118ab2', color3: '#ffd166', number1: '#ffd166', number2: '#073b4c' },
34
+ { color1: '#16425b', color2: '#3a7ca5', color3: '#81c3d7', number1: '#d9dcd6', number2: '#2f6690' },
35
+ { color1: '#ee6c4d', color2: '#3d5a80', color3: '#98c1d9', number1: '#e0fbfc', number2: '#293241' },
36
+ { color1: '#e09f3e', color2: '#335c67', color3: '#9e2a2b', number1: '#fff3b0', number2: '#540b0e' },
37
+ { color1: '#9a031e', color2: '#fb8b24', color3: '#e36414', number1: '#0f4c5c', number2: '#5f0f40' },
38
+ { color1: '#127475', color2: '#f2542d', color3: '#0e9594', number1: '#f5dfbb', number2: '#562c2c' },
39
+ { color1: '#bc4b51', color2: '#8cb369', color3: '#f4a259', number1: '#f4e285', number2: '#5b8e7d' },
40
+ { color1: '#7678ed', color2: '#f18701', color3: '#f35b04', number1: '#f7b801', number2: '#3d348b' },
41
+ { color1: '#14213d', color2: '#fca311', color3: '#e5e5e5', number1: '#ffffff', number2: '#000000' },
42
+ { color1: '#e07a5f', color2: '#81b29a', color3: '#f2cc8f', number1: '#f4f1de', number2: '#3d405b' },
43
+ { color1: '#f79824', color2: '#33a1fd', color3: '#fdca40', number1: '#33a1fd', number2: '#31393c' },
44
+ { color1: '#619b8a', color2: '#fe7f2d', color3: '#a1c181', number1: '#fcca46', number2: '#233d4d' },
45
+ { color1: '#227c9d', color2: '#17c3b2', color3: '#ffcb77', number1: '#fef9ef', number2: '#fe6d73' },
46
+ { color1: '#19647e', color2: '#119da4', color3: '#1f2041', number1: '#4b3f72', number2: '#ffc857' }
47
+ ];
48
+ exports.JERSEY_PATTERN_ORDER = [
49
+ 'solid', 'verticalStripes', 'verticalBars', 'pinstripes', 'hoops', 'checkered', 'diagonalSash', 'splitHalves',
50
+ 'chevron', 'shoulderYoke', 'sidePanels', 'centerBand', 'gradientFade', 'twoToneSplit'
51
+ ];
52
+ exports.JERSEY_CLOTHING_ORDER = [
53
+ 'shirtCrewNeck', 'shirtCrewNeckSleeveless', 'shirtVNeck', 'shirtVNeckSleeveless', 'collarAndSweater', 'collarAndSweaterSleeveless'
54
+ ];
55
+ // Free-user fonts: the id-derived random for a team's default kit picks ONLY from these (premium fonts are vector
56
+ // glyph paths that require the UI rendering pipeline).
57
+ exports.JERSEY_FONT_ORDER = ['sans', 'wide', 'mono', 'serif', 'block'];
58
+ // The full jersey a team wears by default: every category is drawn deterministically from the team id, so each team
59
+ // gets its own stable, distinct kit. Colours come from ONE randomly-assigned JERSEY_PALETTE (harmonious by design);
60
+ // clothing / pattern / font stay random per team.
61
+ function jerseyFromTeamId(teamId) {
62
+ const rng = mulberry32(hashSeed(teamId));
63
+ const pickFrom = (arr) => arr[Math.floor(rng() * arr.length)];
64
+ const palette = pickFrom(exports.JERSEY_PALETTES);
65
+ return {
66
+ clothing: pickFrom(exports.JERSEY_CLOTHING_ORDER),
67
+ pattern: pickFrom(exports.JERSEY_PATTERN_ORDER),
68
+ colors: [palette.color1, palette.color2, palette.color3],
69
+ font: pickFrom(exports.JERSEY_FONT_ORDER),
70
+ numberColor: palette.number1,
71
+ numberOutline: palette.number2,
72
+ numberOutlineEnabled: true,
73
+ numberSpacing: 0,
74
+ numberSize: 46,
75
+ brand: 'none',
76
+ brandColor1: '#ffffff',
77
+ brandColor2: '#1e293b'
78
+ };
79
+ }
@@ -72,3 +72,32 @@ export type JerseyConfig = z.infer<typeof JerseySchema>;
72
72
  export type JerseyClothing = JerseyConfig['clothing'];
73
73
  export type JerseyPattern = JerseyConfig['pattern'];
74
74
  export type JerseyFont = JerseyConfig['font'];
75
+ export declare const AlternateJerseySchema: z.ZodObject<{
76
+ pattern: z.ZodEnum<{
77
+ solid: "solid";
78
+ verticalStripes: "verticalStripes";
79
+ verticalBars: "verticalBars";
80
+ pinstripes: "pinstripes";
81
+ hoops: "hoops";
82
+ checkered: "checkered";
83
+ diagonalSash: "diagonalSash";
84
+ splitHalves: "splitHalves";
85
+ chevron: "chevron";
86
+ shoulderYoke: "shoulderYoke";
87
+ sidePanels: "sidePanels";
88
+ centerBand: "centerBand";
89
+ gradientFade: "gradientFade";
90
+ twoToneSplit: "twoToneSplit";
91
+ }>;
92
+ colors: z.ZodArray<z.ZodString>;
93
+ numberColor: z.ZodString;
94
+ numberOutline: z.ZodString;
95
+ numberOutlineEnabled: z.ZodDefault<z.ZodBoolean>;
96
+ brandColor1: z.ZodDefault<z.ZodString>;
97
+ brandColor2: z.ZodDefault<z.ZodString>;
98
+ }, z.core.$strip>;
99
+ export type AlternateJerseyConfig = z.infer<typeof AlternateJerseySchema>;
100
+ export declare function complementaryHex(hex: string): string;
101
+ export declare function relativeLuminance(hex: string): number;
102
+ export declare function computeComplementaryJersey(main: JerseyConfig): JerseyConfig;
103
+ export declare function mergeAlternateJersey(main: JerseyConfig, alt: AlternateJerseyConfig): JerseyConfig;
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.JerseySchema = void 0;
3
+ exports.AlternateJerseySchema = exports.JerseySchema = void 0;
4
+ exports.complementaryHex = complementaryHex;
5
+ exports.relativeLuminance = relativeLuminance;
6
+ exports.computeComplementaryJersey = computeComplementaryJersey;
7
+ exports.mergeAlternateJersey = mergeAlternateJersey;
4
8
  const zod_1 = require("zod");
5
9
  // 6-digit hex (#rrggbb) or 8-digit hex with alpha (#rrggbbaa). The 8-digit form comes from the UI's opacity-enabled
6
10
  // colour pickers; SVG fills render both natively.
@@ -31,3 +35,123 @@ exports.JerseySchema = zod_1.z.object({
31
35
  brandColor1: hexColor.default('#ffffff'),
32
36
  brandColor2: hexColor.default('#1e293b')
33
37
  });
38
+ // The alternate (libero) kit stores only the fields the premium editor allows changing. Locked fields (clothing,
39
+ // font, brand icon, numberSpacing, numberSize) are always inherited from the main kit at render time.
40
+ exports.AlternateJerseySchema = zod_1.z.object({
41
+ pattern: exports.JerseySchema.shape.pattern,
42
+ colors: exports.JerseySchema.shape.colors,
43
+ numberColor: hexColor,
44
+ numberOutline: hexColor,
45
+ numberOutlineEnabled: zod_1.z.boolean().default(true),
46
+ brandColor1: hexColor.default('#ffffff'),
47
+ brandColor2: hexColor.default('#1e293b')
48
+ });
49
+ // ---------------------------------------------------------------------------
50
+ // Colour utilities (pure math, zero deps)
51
+ // ---------------------------------------------------------------------------
52
+ function hexToRgb(hex) {
53
+ const h = hex.replace('#', '').slice(0, 6);
54
+ return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
55
+ }
56
+ function rgbToHex(r, g, b) {
57
+ return '#' + [r, g, b].map(v => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0')).join('');
58
+ }
59
+ function rgbToHsl(r, g, b) {
60
+ r /= 255;
61
+ g /= 255;
62
+ b /= 255;
63
+ const max = Math.max(r, g, b);
64
+ const min = Math.min(r, g, b);
65
+ const l = (max + min) / 2;
66
+ if (max === min)
67
+ return [0, 0, l];
68
+ const d = max - min;
69
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
70
+ let h = 0;
71
+ if (max === r)
72
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
73
+ else if (max === g)
74
+ h = ((b - r) / d + 2) / 6;
75
+ else
76
+ h = ((r - g) / d + 4) / 6;
77
+ return [h, s, l];
78
+ }
79
+ function hslToRgb(h, s, l) {
80
+ if (s === 0) {
81
+ const v = Math.round(l * 255);
82
+ return [v, v, v];
83
+ }
84
+ const hue2rgb = (p, q, t) => {
85
+ if (t < 0)
86
+ t += 1;
87
+ if (t > 1)
88
+ t -= 1;
89
+ if (t < 1 / 6)
90
+ return p + (q - p) * 6 * t;
91
+ if (t < 1 / 2)
92
+ return q;
93
+ if (t < 2 / 3)
94
+ return p + (q - p) * (2 / 3 - t) * 6;
95
+ return p;
96
+ };
97
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
98
+ const p = 2 * l - q;
99
+ return [Math.round(hue2rgb(p, q, h + 1 / 3) * 255), Math.round(hue2rgb(p, q, h) * 255), Math.round(hue2rgb(p, q, h - 1 / 3) * 255)];
100
+ }
101
+ // Rotate a hex colour's hue by 180 degrees (complementary).
102
+ function complementaryHex(hex) {
103
+ const [r, g, b] = hexToRgb(hex);
104
+ const [h, s, l] = rgbToHsl(r, g, b);
105
+ const [cr, cg, cb] = hslToRgb((h + 0.5) % 1, s, l);
106
+ return rgbToHex(cr, cg, cb);
107
+ }
108
+ // Relative luminance (WCAG 2.x formula, 0 = black, 1 = white).
109
+ function relativeLuminance(hex) {
110
+ const [r, g, b] = hexToRgb(hex);
111
+ const srgb = [r, g, b].map(v => {
112
+ v /= 255;
113
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
114
+ });
115
+ return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
116
+ }
117
+ // Build the libero alternate kit from the main kit using complementary colours. Every colour in the
118
+ // pattern and the brand is hue-rotated 180 degrees; the chest-number fill/outline are picked for
119
+ // readability against the new primary colour (dark number on light, light number on dark).
120
+ function computeComplementaryJersey(main) {
121
+ const compColors = main.colors.map(c => complementaryHex(c));
122
+ const lum = relativeLuminance(compColors[0]);
123
+ return {
124
+ // Locked fields: inherited from main
125
+ clothing: main.clothing,
126
+ font: main.font,
127
+ brand: main.brand,
128
+ numberSpacing: main.numberSpacing,
129
+ numberSize: main.numberSize,
130
+ // Editable fields: complementary-derived
131
+ pattern: main.pattern,
132
+ colors: compColors,
133
+ numberColor: lum > 0.4 ? '#1a1a1a' : '#ffffff',
134
+ numberOutline: lum > 0.4 ? '#ffffff' : '#1a1a1a',
135
+ numberOutlineEnabled: main.numberOutlineEnabled,
136
+ brandColor1: complementaryHex(main.brandColor1),
137
+ brandColor2: complementaryHex(main.brandColor2)
138
+ };
139
+ }
140
+ // Merge a stored alternate (partial, editable fields only) with the main kit's locked fields to
141
+ // produce a full JerseyConfig for rendering.
142
+ function mergeAlternateJersey(main, alt) {
143
+ return {
144
+ clothing: main.clothing,
145
+ font: main.font,
146
+ brand: main.brand,
147
+ numberSpacing: main.numberSpacing,
148
+ numberSize: main.numberSize,
149
+ pattern: alt.pattern,
150
+ colors: alt.colors,
151
+ numberColor: alt.numberColor,
152
+ numberOutline: alt.numberOutline,
153
+ numberOutlineEnabled: alt.numberOutlineEnabled,
154
+ brandColor1: alt.brandColor1,
155
+ brandColor2: alt.brandColor2
156
+ };
157
+ }
@@ -414,5 +414,29 @@ export declare const TeamInputSchema: z.ZodObject<{
414
414
  brandColor1: z.ZodDefault<z.ZodString>;
415
415
  brandColor2: z.ZodDefault<z.ZodString>;
416
416
  }, z.core.$strip>>;
417
+ alternateJerseyConfig: z.ZodOptional<z.ZodObject<{
418
+ pattern: z.ZodEnum<{
419
+ solid: "solid";
420
+ verticalStripes: "verticalStripes";
421
+ verticalBars: "verticalBars";
422
+ pinstripes: "pinstripes";
423
+ hoops: "hoops";
424
+ checkered: "checkered";
425
+ diagonalSash: "diagonalSash";
426
+ splitHalves: "splitHalves";
427
+ chevron: "chevron";
428
+ shoulderYoke: "shoulderYoke";
429
+ sidePanels: "sidePanels";
430
+ centerBand: "centerBand";
431
+ gradientFade: "gradientFade";
432
+ twoToneSplit: "twoToneSplit";
433
+ }>;
434
+ colors: z.ZodArray<z.ZodString>;
435
+ numberColor: z.ZodString;
436
+ numberOutline: z.ZodString;
437
+ numberOutlineEnabled: z.ZodDefault<z.ZodBoolean>;
438
+ brandColor1: z.ZodDefault<z.ZodString>;
439
+ brandColor2: z.ZodDefault<z.ZodString>;
440
+ }, z.core.$strip>>;
417
441
  }, z.core.$strip>;
418
442
  export type TeamInput = z.infer<typeof TeamInputSchema>;
@@ -22,7 +22,8 @@ exports.TeamInputSchema = zod_1.z.object({
22
22
  country: countrySchema,
23
23
  legacyFlagCountry: countrySchema.optional(),
24
24
  tactics: tactics_z_1.TacticsInputSchema.optional(),
25
- jerseyConfig: jersey_1.JerseySchema.optional()
25
+ jerseyConfig: jersey_1.JerseySchema.optional(),
26
+ alternateJerseyConfig: jersey_1.AlternateJerseySchema.optional()
26
27
  }).superRefine((data, ctx) => {
27
28
  const ids = data.roster.map(p => p.id);
28
29
  if (new Set(ids).size !== ids.length) {
@@ -1,7 +1,7 @@
1
1
  import { Player } from '../player';
2
2
  import { Country } from '../country';
3
3
  import { Tactics } from './tactics';
4
- import type { JerseyConfig } from './jersey';
4
+ import type { JerseyConfig, AlternateJerseyConfig } from './jersey';
5
5
  export declare class Team {
6
6
  readonly id: string;
7
7
  readonly roster: Player[];
@@ -12,6 +12,7 @@ export declare class Team {
12
12
  readonly country: Country;
13
13
  readonly legacyFlagCountry?: Country;
14
14
  readonly jerseyConfig?: JerseyConfig;
15
+ readonly alternateJerseyConfig?: AlternateJerseyConfig;
15
16
  private _rating;
16
17
  static create(input: unknown): Team;
17
18
  private constructor();
@@ -19,7 +19,7 @@ class Team {
19
19
  tactics: result.data.tactics != null ? tactics_1.Tactics.create(result.data.tactics) : undefined
20
20
  });
21
21
  }
22
- constructor({ id, rating, name, shortName, divisionId, country, legacyFlagCountry, roster, tactics, jerseyConfig }) {
22
+ constructor({ id, rating, name, shortName, divisionId, country, legacyFlagCountry, roster, tactics, jerseyConfig, alternateJerseyConfig }) {
23
23
  this.id = id;
24
24
  this._rating = rating;
25
25
  this.roster = roster;
@@ -30,6 +30,7 @@ class Team {
30
30
  this.country = country;
31
31
  this.legacyFlagCountry = legacyFlagCountry;
32
32
  this.jerseyConfig = jerseyConfig;
33
+ this.alternateJerseyConfig = alternateJerseyConfig;
33
34
  }
34
35
  get rating() {
35
36
  return this._rating;
@@ -196,7 +196,7 @@ export type Team = Omit<DataProps<_Team>, 'roster' | '_rating' | 'rating' | 'tac
196
196
  nationalManager?: {
197
197
  displayName: string;
198
198
  teamId: string;
199
- teamShortName: string;
199
+ teamName: string;
200
200
  };
201
201
  };
202
202
  export type Standing = DataProps<_Standing> & {
@@ -1,10 +1,11 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model } from 'sequelize';
3
3
  import { TeamId, TeamModel } from '.';
4
- import { JerseyConfig } from '../../service';
4
+ import { JerseyConfig, AlternateJerseyConfig } from '../../service';
5
5
  export interface TeamJerseyAttributes {
6
6
  team_id: string;
7
7
  config: JerseyConfig;
8
+ alternate_config?: AlternateJerseyConfig | null;
8
9
  }
9
10
  export type TeamJerseyPk = 'team_id';
10
11
  export type TeamJerseyId = TeamJerseyModel[TeamJerseyPk];
@@ -12,6 +13,7 @@ export type TeamJerseyCreationAttributes = TeamJerseyAttributes;
12
13
  export declare class TeamJerseyModel extends Model<TeamJerseyAttributes, TeamJerseyCreationAttributes> implements TeamJerseyAttributes {
13
14
  team_id: string;
14
15
  config: JerseyConfig;
16
+ alternate_config?: AlternateJerseyConfig | null;
15
17
  team: TeamModel;
16
18
  getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
17
19
  setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
@@ -14,6 +14,10 @@ export class TeamJerseyModel extends Model {
14
14
  config: {
15
15
  type: DataTypes.JSONB,
16
16
  allowNull: false
17
+ },
18
+ alternate_config: {
19
+ type: DataTypes.JSONB,
20
+ allowNull: true
17
21
  }
18
22
  }, {
19
23
  sequelize,
@@ -46,7 +46,8 @@ function transformToObject(model, currentIteration, tacticsOverride) {
46
46
  divisionId: model.division_id,
47
47
  // Present only when the caller explicitly included the TeamJersey association (editor / effective-jersey
48
48
  // resolver); undefined on ordinary team loads, which heals to the id-derived default in the UI.
49
- jerseyConfig: model.TeamJersey?.config
49
+ jerseyConfig: model.TeamJersey?.config,
50
+ alternateJerseyConfig: model.TeamJersey?.alternate_config ?? undefined
50
51
  });
51
52
  }
52
53
  export { transformToObject as transformToTeam, transformToAttributes as transformFromTeam };
@@ -1,5 +1,6 @@
1
1
  export * from './team';
2
2
  export * from './jersey';
3
+ export * from './jersey-defaults';
3
4
  export * from './team-generator';
4
5
  export * from './formation';
5
6
  export * from './tactics';
@@ -1,5 +1,6 @@
1
1
  export * from './team';
2
2
  export * from './jersey';
3
+ export * from './jersey-defaults';
3
4
  export * from './team-generator';
4
5
  export * from './formation';
5
6
  export * from './tactics';
@@ -0,0 +1,13 @@
1
+ import type { JerseyConfig, JerseyClothing, JerseyPattern, JerseyFont } from './jersey';
2
+ export interface JerseyPalette {
3
+ color1: string;
4
+ color2: string;
5
+ color3: string;
6
+ number1: string;
7
+ number2: string;
8
+ }
9
+ export declare const JERSEY_PALETTES: readonly JerseyPalette[];
10
+ export declare const JERSEY_PATTERN_ORDER: readonly JerseyPattern[];
11
+ export declare const JERSEY_CLOTHING_ORDER: readonly JerseyClothing[];
12
+ export declare const JERSEY_FONT_ORDER: readonly JerseyFont[];
13
+ export declare function jerseyFromTeamId(teamId: string): JerseyConfig;
@@ -0,0 +1,75 @@
1
+ // FNV-1a hash of a string, used to seed the PRNG below from a team id.
2
+ function hashSeed(str) {
3
+ let h = 2166136261;
4
+ for (let i = 0; i < str.length; i++) {
5
+ h ^= str.charCodeAt(i);
6
+ h = Math.imul(h, 16777619);
7
+ }
8
+ return h >>> 0;
9
+ }
10
+ // mulberry32: a tiny deterministic PRNG. Same seed -> same stream, so a team's kit is stable and reproducible.
11
+ function mulberry32(seed) {
12
+ let a = seed;
13
+ return () => {
14
+ a = (a + 0x6d2b79f5) | 0;
15
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
16
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
17
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
18
+ };
19
+ }
20
+ export const JERSEY_PALETTES = [
21
+ { color1: '#606c38', color2: '#dda15e', color3: '#bc6c25', number1: '#fefae0', number2: '#283618' },
22
+ { color1: '#780000', color2: '#669bbc', color3: '#c1121f', number1: '#fdf0d5', number2: '#003049' },
23
+ { color1: '#2a9d8f', color2: '#f4a261', color3: '#e76f51', number1: '#264653', number2: '#e9c46a' },
24
+ { color1: '#219ebc', color2: '#fb8500', color3: '#8ecae6', number1: '#023047', number2: '#ffb703' },
25
+ { color1: '#1d3557', color2: '#e63946', color3: '#a8dadc', number1: '#f1faee', number2: '#457b9d' },
26
+ { color1: '#003566', color2: '#ffc300', color3: '#001d3d', number1: '#ffd60a', number2: '#000814' },
27
+ { color1: '#bc4749', color2: '#6a994e', color3: '#a7c957', number1: '#f2e8cf', number2: '#386641' },
28
+ { color1: '#6f1d1b', color2: '#99582a', color3: '#bb9457', number1: '#ffe6a7', number2: '#432818' },
29
+ { color1: '#ef476f', color2: '#118ab2', color3: '#ffd166', number1: '#ffd166', number2: '#073b4c' },
30
+ { color1: '#16425b', color2: '#3a7ca5', color3: '#81c3d7', number1: '#d9dcd6', number2: '#2f6690' },
31
+ { color1: '#ee6c4d', color2: '#3d5a80', color3: '#98c1d9', number1: '#e0fbfc', number2: '#293241' },
32
+ { color1: '#e09f3e', color2: '#335c67', color3: '#9e2a2b', number1: '#fff3b0', number2: '#540b0e' },
33
+ { color1: '#9a031e', color2: '#fb8b24', color3: '#e36414', number1: '#0f4c5c', number2: '#5f0f40' },
34
+ { color1: '#127475', color2: '#f2542d', color3: '#0e9594', number1: '#f5dfbb', number2: '#562c2c' },
35
+ { color1: '#bc4b51', color2: '#8cb369', color3: '#f4a259', number1: '#f4e285', number2: '#5b8e7d' },
36
+ { color1: '#7678ed', color2: '#f18701', color3: '#f35b04', number1: '#f7b801', number2: '#3d348b' },
37
+ { color1: '#14213d', color2: '#fca311', color3: '#e5e5e5', number1: '#ffffff', number2: '#000000' },
38
+ { color1: '#e07a5f', color2: '#81b29a', color3: '#f2cc8f', number1: '#f4f1de', number2: '#3d405b' },
39
+ { color1: '#f79824', color2: '#33a1fd', color3: '#fdca40', number1: '#33a1fd', number2: '#31393c' },
40
+ { color1: '#619b8a', color2: '#fe7f2d', color3: '#a1c181', number1: '#fcca46', number2: '#233d4d' },
41
+ { color1: '#227c9d', color2: '#17c3b2', color3: '#ffcb77', number1: '#fef9ef', number2: '#fe6d73' },
42
+ { color1: '#19647e', color2: '#119da4', color3: '#1f2041', number1: '#4b3f72', number2: '#ffc857' }
43
+ ];
44
+ export const JERSEY_PATTERN_ORDER = [
45
+ 'solid', 'verticalStripes', 'verticalBars', 'pinstripes', 'hoops', 'checkered', 'diagonalSash', 'splitHalves',
46
+ 'chevron', 'shoulderYoke', 'sidePanels', 'centerBand', 'gradientFade', 'twoToneSplit'
47
+ ];
48
+ export const JERSEY_CLOTHING_ORDER = [
49
+ 'shirtCrewNeck', 'shirtCrewNeckSleeveless', 'shirtVNeck', 'shirtVNeckSleeveless', 'collarAndSweater', 'collarAndSweaterSleeveless'
50
+ ];
51
+ // Free-user fonts: the id-derived random for a team's default kit picks ONLY from these (premium fonts are vector
52
+ // glyph paths that require the UI rendering pipeline).
53
+ export const JERSEY_FONT_ORDER = ['sans', 'wide', 'mono', 'serif', 'block'];
54
+ // The full jersey a team wears by default: every category is drawn deterministically from the team id, so each team
55
+ // gets its own stable, distinct kit. Colours come from ONE randomly-assigned JERSEY_PALETTE (harmonious by design);
56
+ // clothing / pattern / font stay random per team.
57
+ export function jerseyFromTeamId(teamId) {
58
+ const rng = mulberry32(hashSeed(teamId));
59
+ const pickFrom = (arr) => arr[Math.floor(rng() * arr.length)];
60
+ const palette = pickFrom(JERSEY_PALETTES);
61
+ return {
62
+ clothing: pickFrom(JERSEY_CLOTHING_ORDER),
63
+ pattern: pickFrom(JERSEY_PATTERN_ORDER),
64
+ colors: [palette.color1, palette.color2, palette.color3],
65
+ font: pickFrom(JERSEY_FONT_ORDER),
66
+ numberColor: palette.number1,
67
+ numberOutline: palette.number2,
68
+ numberOutlineEnabled: true,
69
+ numberSpacing: 0,
70
+ numberSize: 46,
71
+ brand: 'none',
72
+ brandColor1: '#ffffff',
73
+ brandColor2: '#1e293b'
74
+ };
75
+ }
@@ -72,3 +72,32 @@ export type JerseyConfig = z.infer<typeof JerseySchema>;
72
72
  export type JerseyClothing = JerseyConfig['clothing'];
73
73
  export type JerseyPattern = JerseyConfig['pattern'];
74
74
  export type JerseyFont = JerseyConfig['font'];
75
+ export declare const AlternateJerseySchema: z.ZodObject<{
76
+ pattern: z.ZodEnum<{
77
+ solid: "solid";
78
+ verticalStripes: "verticalStripes";
79
+ verticalBars: "verticalBars";
80
+ pinstripes: "pinstripes";
81
+ hoops: "hoops";
82
+ checkered: "checkered";
83
+ diagonalSash: "diagonalSash";
84
+ splitHalves: "splitHalves";
85
+ chevron: "chevron";
86
+ shoulderYoke: "shoulderYoke";
87
+ sidePanels: "sidePanels";
88
+ centerBand: "centerBand";
89
+ gradientFade: "gradientFade";
90
+ twoToneSplit: "twoToneSplit";
91
+ }>;
92
+ colors: z.ZodArray<z.ZodString>;
93
+ numberColor: z.ZodString;
94
+ numberOutline: z.ZodString;
95
+ numberOutlineEnabled: z.ZodDefault<z.ZodBoolean>;
96
+ brandColor1: z.ZodDefault<z.ZodString>;
97
+ brandColor2: z.ZodDefault<z.ZodString>;
98
+ }, z.core.$strip>;
99
+ export type AlternateJerseyConfig = z.infer<typeof AlternateJerseySchema>;
100
+ export declare function complementaryHex(hex: string): string;
101
+ export declare function relativeLuminance(hex: string): number;
102
+ export declare function computeComplementaryJersey(main: JerseyConfig): JerseyConfig;
103
+ export declare function mergeAlternateJersey(main: JerseyConfig, alt: AlternateJerseyConfig): JerseyConfig;
@@ -28,3 +28,123 @@ export const JerseySchema = z.object({
28
28
  brandColor1: hexColor.default('#ffffff'),
29
29
  brandColor2: hexColor.default('#1e293b')
30
30
  });
31
+ // The alternate (libero) kit stores only the fields the premium editor allows changing. Locked fields (clothing,
32
+ // font, brand icon, numberSpacing, numberSize) are always inherited from the main kit at render time.
33
+ export const AlternateJerseySchema = z.object({
34
+ pattern: JerseySchema.shape.pattern,
35
+ colors: JerseySchema.shape.colors,
36
+ numberColor: hexColor,
37
+ numberOutline: hexColor,
38
+ numberOutlineEnabled: z.boolean().default(true),
39
+ brandColor1: hexColor.default('#ffffff'),
40
+ brandColor2: hexColor.default('#1e293b')
41
+ });
42
+ // ---------------------------------------------------------------------------
43
+ // Colour utilities (pure math, zero deps)
44
+ // ---------------------------------------------------------------------------
45
+ function hexToRgb(hex) {
46
+ const h = hex.replace('#', '').slice(0, 6);
47
+ return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
48
+ }
49
+ function rgbToHex(r, g, b) {
50
+ return '#' + [r, g, b].map(v => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0')).join('');
51
+ }
52
+ function rgbToHsl(r, g, b) {
53
+ r /= 255;
54
+ g /= 255;
55
+ b /= 255;
56
+ const max = Math.max(r, g, b);
57
+ const min = Math.min(r, g, b);
58
+ const l = (max + min) / 2;
59
+ if (max === min)
60
+ return [0, 0, l];
61
+ const d = max - min;
62
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
63
+ let h = 0;
64
+ if (max === r)
65
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
66
+ else if (max === g)
67
+ h = ((b - r) / d + 2) / 6;
68
+ else
69
+ h = ((r - g) / d + 4) / 6;
70
+ return [h, s, l];
71
+ }
72
+ function hslToRgb(h, s, l) {
73
+ if (s === 0) {
74
+ const v = Math.round(l * 255);
75
+ return [v, v, v];
76
+ }
77
+ const hue2rgb = (p, q, t) => {
78
+ if (t < 0)
79
+ t += 1;
80
+ if (t > 1)
81
+ t -= 1;
82
+ if (t < 1 / 6)
83
+ return p + (q - p) * 6 * t;
84
+ if (t < 1 / 2)
85
+ return q;
86
+ if (t < 2 / 3)
87
+ return p + (q - p) * (2 / 3 - t) * 6;
88
+ return p;
89
+ };
90
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
91
+ const p = 2 * l - q;
92
+ return [Math.round(hue2rgb(p, q, h + 1 / 3) * 255), Math.round(hue2rgb(p, q, h) * 255), Math.round(hue2rgb(p, q, h - 1 / 3) * 255)];
93
+ }
94
+ // Rotate a hex colour's hue by 180 degrees (complementary).
95
+ export function complementaryHex(hex) {
96
+ const [r, g, b] = hexToRgb(hex);
97
+ const [h, s, l] = rgbToHsl(r, g, b);
98
+ const [cr, cg, cb] = hslToRgb((h + 0.5) % 1, s, l);
99
+ return rgbToHex(cr, cg, cb);
100
+ }
101
+ // Relative luminance (WCAG 2.x formula, 0 = black, 1 = white).
102
+ export function relativeLuminance(hex) {
103
+ const [r, g, b] = hexToRgb(hex);
104
+ const srgb = [r, g, b].map(v => {
105
+ v /= 255;
106
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
107
+ });
108
+ return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
109
+ }
110
+ // Build the libero alternate kit from the main kit using complementary colours. Every colour in the
111
+ // pattern and the brand is hue-rotated 180 degrees; the chest-number fill/outline are picked for
112
+ // readability against the new primary colour (dark number on light, light number on dark).
113
+ export function computeComplementaryJersey(main) {
114
+ const compColors = main.colors.map(c => complementaryHex(c));
115
+ const lum = relativeLuminance(compColors[0]);
116
+ return {
117
+ // Locked fields: inherited from main
118
+ clothing: main.clothing,
119
+ font: main.font,
120
+ brand: main.brand,
121
+ numberSpacing: main.numberSpacing,
122
+ numberSize: main.numberSize,
123
+ // Editable fields: complementary-derived
124
+ pattern: main.pattern,
125
+ colors: compColors,
126
+ numberColor: lum > 0.4 ? '#1a1a1a' : '#ffffff',
127
+ numberOutline: lum > 0.4 ? '#ffffff' : '#1a1a1a',
128
+ numberOutlineEnabled: main.numberOutlineEnabled,
129
+ brandColor1: complementaryHex(main.brandColor1),
130
+ brandColor2: complementaryHex(main.brandColor2)
131
+ };
132
+ }
133
+ // Merge a stored alternate (partial, editable fields only) with the main kit's locked fields to
134
+ // produce a full JerseyConfig for rendering.
135
+ export function mergeAlternateJersey(main, alt) {
136
+ return {
137
+ clothing: main.clothing,
138
+ font: main.font,
139
+ brand: main.brand,
140
+ numberSpacing: main.numberSpacing,
141
+ numberSize: main.numberSize,
142
+ pattern: alt.pattern,
143
+ colors: alt.colors,
144
+ numberColor: alt.numberColor,
145
+ numberOutline: alt.numberOutline,
146
+ numberOutlineEnabled: alt.numberOutlineEnabled,
147
+ brandColor1: alt.brandColor1,
148
+ brandColor2: alt.brandColor2
149
+ };
150
+ }
@@ -414,5 +414,29 @@ export declare const TeamInputSchema: z.ZodObject<{
414
414
  brandColor1: z.ZodDefault<z.ZodString>;
415
415
  brandColor2: z.ZodDefault<z.ZodString>;
416
416
  }, z.core.$strip>>;
417
+ alternateJerseyConfig: z.ZodOptional<z.ZodObject<{
418
+ pattern: z.ZodEnum<{
419
+ solid: "solid";
420
+ verticalStripes: "verticalStripes";
421
+ verticalBars: "verticalBars";
422
+ pinstripes: "pinstripes";
423
+ hoops: "hoops";
424
+ checkered: "checkered";
425
+ diagonalSash: "diagonalSash";
426
+ splitHalves: "splitHalves";
427
+ chevron: "chevron";
428
+ shoulderYoke: "shoulderYoke";
429
+ sidePanels: "sidePanels";
430
+ centerBand: "centerBand";
431
+ gradientFade: "gradientFade";
432
+ twoToneSplit: "twoToneSplit";
433
+ }>;
434
+ colors: z.ZodArray<z.ZodString>;
435
+ numberColor: z.ZodString;
436
+ numberOutline: z.ZodString;
437
+ numberOutlineEnabled: z.ZodDefault<z.ZodBoolean>;
438
+ brandColor1: z.ZodDefault<z.ZodString>;
439
+ brandColor2: z.ZodDefault<z.ZodString>;
440
+ }, z.core.$strip>>;
417
441
  }, z.core.$strip>;
418
442
  export type TeamInput = z.infer<typeof TeamInputSchema>;
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { Player } from '../../player';
3
3
  import { Country } from '../../country';
4
4
  import { TacticsInputSchema } from './tactics.z';
5
- import { JerseySchema } from '../jersey';
5
+ import { JerseySchema, AlternateJerseySchema } from '../jersey';
6
6
  const playerInstanceSchema = z.custom((v) => v instanceof Player, {
7
7
  message: 'INVALID_PLAYER_INSTANCE'
8
8
  });
@@ -19,7 +19,8 @@ export const TeamInputSchema = z.object({
19
19
  country: countrySchema,
20
20
  legacyFlagCountry: countrySchema.optional(),
21
21
  tactics: TacticsInputSchema.optional(),
22
- jerseyConfig: JerseySchema.optional()
22
+ jerseyConfig: JerseySchema.optional(),
23
+ alternateJerseyConfig: AlternateJerseySchema.optional()
23
24
  }).superRefine((data, ctx) => {
24
25
  const ids = data.roster.map(p => p.id);
25
26
  if (new Set(ids).size !== ids.length) {
@@ -1,7 +1,7 @@
1
1
  import { Player } from '../player';
2
2
  import { Country } from '../country';
3
3
  import { Tactics } from './tactics';
4
- import type { JerseyConfig } from './jersey';
4
+ import type { JerseyConfig, AlternateJerseyConfig } from './jersey';
5
5
  export declare class Team {
6
6
  readonly id: string;
7
7
  readonly roster: Player[];
@@ -12,6 +12,7 @@ export declare class Team {
12
12
  readonly country: Country;
13
13
  readonly legacyFlagCountry?: Country;
14
14
  readonly jerseyConfig?: JerseyConfig;
15
+ readonly alternateJerseyConfig?: AlternateJerseyConfig;
15
16
  private _rating;
16
17
  static create(input: unknown): Team;
17
18
  private constructor();
@@ -16,7 +16,7 @@ export class Team {
16
16
  tactics: result.data.tactics != null ? Tactics.create(result.data.tactics) : undefined
17
17
  });
18
18
  }
19
- constructor({ id, rating, name, shortName, divisionId, country, legacyFlagCountry, roster, tactics, jerseyConfig }) {
19
+ constructor({ id, rating, name, shortName, divisionId, country, legacyFlagCountry, roster, tactics, jerseyConfig, alternateJerseyConfig }) {
20
20
  this.id = id;
21
21
  this._rating = rating;
22
22
  this.roster = roster;
@@ -27,6 +27,7 @@ export class Team {
27
27
  this.country = country;
28
28
  this.legacyFlagCountry = legacyFlagCountry;
29
29
  this.jerseyConfig = jerseyConfig;
30
+ this.alternateJerseyConfig = alternateJerseyConfig;
30
31
  }
31
32
  get rating() {
32
33
  return this._rating;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volleyballsimtypes",
3
- "version": "0.0.526",
3
+ "version": "0.0.527",
4
4
  "description": "vbsim types",
5
5
  "main": "./dist/cjs/src/index.js",
6
6
  "module": "./dist/esm/src/index.js",