@ankhorage/zora-tabletop 0.0.0 → 0.0.2

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 ADDED
@@ -0,0 +1,13 @@
1
+ # @ankhorage/zora-tabletop
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 3b3651b: Improve tabletop label readability by deriving contrast-aware text colors for table and seat surfaces.
8
+
9
+ ## 0.0.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 58b3a2e: Add initial tabletop and card-game presentation primitives.
package/dist/colors.d.ts CHANGED
@@ -7,11 +7,15 @@ export interface TabletopColorScheme {
7
7
  readonly mutedText: string;
8
8
  readonly redSuitText: string;
9
9
  readonly seatBorder: string;
10
+ readonly seatMutedText: string;
10
11
  readonly seatSelectedBorder: string;
11
12
  readonly seatSurface: string;
13
+ readonly seatText: string;
12
14
  readonly tableBorder: string;
13
15
  readonly tableFelt: string;
14
16
  readonly tableInnerBorder: string;
17
+ readonly tableMutedText: string;
18
+ readonly tableText: string;
15
19
  readonly tokenSurface: string;
16
20
  readonly tokenText: string;
17
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAElE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE;QACT,MAAM,EAAE;YACN,OAAO,EAAE;gBACP,IAAI,EAAE,MAAM,CAAC;gBACb,MAAM,EAAE,MAAM,CAAC;aAChB,CAAC;SACH,CAAC;QACF,OAAO,EAAE;YACP,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;QACF,OAAO,EAAE;YACP,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,EAAE,MAAM,CAAC;YAChB,OAAO,EAAE,MAAM,CAAC;YAChB,YAAY,EAAE,MAAM,CAAC;SACtB,CAAC;QACF,OAAO,EAAE;YACP,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,EAAE,MAAM,CAAC;SAChB,CAAC;KACH,CAAC;CACH;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,uBAAuB,EAC9B,SAAS,GAAE,sBAA2B,GACrC,mBAAmB,CAkBrB"}
1
+ {"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAElE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE;QACT,MAAM,EAAE;YACN,OAAO,EAAE;gBACP,IAAI,EAAE,MAAM,CAAC;gBACb,MAAM,EAAE,MAAM,CAAC;aAChB,CAAC;SACH,CAAC;QACF,OAAO,EAAE;YACP,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;QACF,OAAO,EAAE;YACP,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,EAAE,MAAM,CAAC;YAChB,OAAO,EAAE,MAAM,CAAC;YAChB,YAAY,EAAE,MAAM,CAAC;SACtB,CAAC;QACF,OAAO,EAAE;YACP,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,EAAE,MAAM,CAAC;SAChB,CAAC;KACH,CAAC;CACH;AAYD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,uBAAuB,EAC9B,SAAS,GAAE,sBAA2B,GACrC,mBAAmB,CAgDrB"}
package/dist/colors.js CHANGED
@@ -1,20 +1,115 @@
1
+ const DARK_TEXT_FALLBACK = '#111827';
2
+ const LIGHT_TEXT_FALLBACK = '#ffffff';
3
+ const MINIMUM_READABLE_CONTRAST = 4.5;
1
4
  export function createTabletopColorScheme(theme, overrides = {}) {
5
+ const cardSurface = overrides.cardSurface ?? theme.semantics.neutral.surface;
6
+ const seatSurface = overrides.seatSurface ?? theme.semantics.neutral.surface;
7
+ const tableFelt = overrides.tableFelt ?? theme.semantics.action.primary.softBg;
8
+ const tokenSurface = overrides.tokenSurface ?? theme.semantics.warning.softBg;
9
+ const cardText = overrides.cardText ??
10
+ resolveReadableTextColor(cardSurface, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);
11
+ const seatText = overrides.seatText ??
12
+ resolveReadableTextColor(seatSurface, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);
13
+ const tableText = overrides.tableText ??
14
+ resolveReadableTextColor(tableFelt, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);
15
+ const tokenText = overrides.tokenText ??
16
+ resolveReadableTextColor(tokenSurface, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);
2
17
  return {
3
18
  cardBack: overrides.cardBack ?? theme.semantics.warning.base,
4
19
  cardBackBorder: overrides.cardBackBorder ?? theme.semantics.neutral.border,
5
20
  cardBorder: overrides.cardBorder ?? theme.semantics.neutral.border,
6
- cardSurface: overrides.cardSurface ?? theme.semantics.neutral.surface,
7
- cardText: overrides.cardText ?? theme.semantics.content.default,
8
- mutedText: overrides.mutedText ?? theme.semantics.content.muted,
9
- redSuitText: overrides.redSuitText ?? theme.semantics.warning.base,
21
+ cardSurface,
22
+ cardText,
23
+ mutedText: overrides.mutedText ??
24
+ resolveReadableTextColor(cardSurface, theme.semantics.content.muted, cardText),
25
+ redSuitText: overrides.redSuitText ??
26
+ resolveReadableTextColor(cardSurface, theme.semantics.warning.base, cardText),
10
27
  seatBorder: overrides.seatBorder ?? theme.semantics.neutral.divider,
28
+ seatMutedText: overrides.seatMutedText ??
29
+ resolveReadableTextColor(seatSurface, theme.semantics.content.muted, seatText),
11
30
  seatSelectedBorder: overrides.seatSelectedBorder ?? theme.semantics.action.primary.base,
12
- seatSurface: overrides.seatSurface ?? theme.semantics.neutral.surface,
31
+ seatSurface,
32
+ seatText,
13
33
  tableBorder: overrides.tableBorder ?? theme.semantics.warning.base,
14
- tableFelt: overrides.tableFelt ?? theme.semantics.action.primary.softBg,
34
+ tableFelt,
15
35
  tableInnerBorder: overrides.tableInnerBorder ?? theme.semantics.neutral.divider,
16
- tokenSurface: overrides.tokenSurface ?? theme.semantics.warning.softBg,
17
- tokenText: overrides.tokenText ?? theme.semantics.content.default,
36
+ tableMutedText: overrides.tableMutedText ??
37
+ resolveReadableTextColor(tableFelt, theme.semantics.content.muted, tableText),
38
+ tableText,
39
+ tokenSurface,
40
+ tokenText,
18
41
  };
19
42
  }
43
+ function resolveReadableTextColor(background, preferred, fallback) {
44
+ const preferredContrast = getContrastRatio(background, preferred);
45
+ if (preferredContrast !== null && preferredContrast >= MINIMUM_READABLE_CONTRAST) {
46
+ return preferred;
47
+ }
48
+ const candidates = uniqueColors([preferred, fallback, LIGHT_TEXT_FALLBACK, DARK_TEXT_FALLBACK]);
49
+ const [readableCandidate] = candidates
50
+ .map((candidate) => ({ candidate, contrast: getContrastRatio(background, candidate) }))
51
+ .filter((entry) => entry.contrast !== null)
52
+ .sort((left, right) => right.contrast - left.contrast);
53
+ return readableCandidate?.candidate ?? preferred;
54
+ }
55
+ function uniqueColors(colors) {
56
+ return [...new Set(colors)];
57
+ }
58
+ function getContrastRatio(background, foreground) {
59
+ const backgroundColor = parseHexColor(background);
60
+ const foregroundColor = parseHexColor(foreground);
61
+ if (backgroundColor === null || foregroundColor === null)
62
+ return null;
63
+ const backgroundLuminance = getRelativeLuminance(backgroundColor);
64
+ const foregroundLuminance = getRelativeLuminance(foregroundColor);
65
+ const lighter = Math.max(backgroundLuminance, foregroundLuminance);
66
+ const darker = Math.min(backgroundLuminance, foregroundLuminance);
67
+ return (lighter + 0.05) / (darker + 0.05);
68
+ }
69
+ function parseHexColor(color) {
70
+ const value = color.trim();
71
+ const hex = value.startsWith('#') ? value.slice(1) : '';
72
+ if (!isSupportedHexLength(hex) || !isHexColorValue(hex))
73
+ return null;
74
+ if (hex.length === 3) {
75
+ const red = hex.slice(0, 1);
76
+ const green = hex.slice(1, 2);
77
+ const blue = hex.slice(2, 3);
78
+ return {
79
+ red: parseInt(red + red, 16),
80
+ green: parseInt(green + green, 16),
81
+ blue: parseInt(blue + blue, 16),
82
+ };
83
+ }
84
+ return {
85
+ red: parseInt(hex.slice(0, 2), 16),
86
+ green: parseInt(hex.slice(2, 4), 16),
87
+ blue: parseInt(hex.slice(4, 6), 16),
88
+ };
89
+ }
90
+ function isSupportedHexLength(hex) {
91
+ return hex.length === 3 || hex.length === 6 || hex.length === 8;
92
+ }
93
+ function isHexColorValue(hex) {
94
+ return [...hex].every((character) => isHexCharacter(character));
95
+ }
96
+ function isHexCharacter(character) {
97
+ const code = character.charCodeAt(0);
98
+ const isDigit = code >= 48 && code <= 57;
99
+ const isUpperHex = code >= 65 && code <= 70;
100
+ const isLowerHex = code >= 97 && code <= 102;
101
+ return isDigit || isUpperHex || isLowerHex;
102
+ }
103
+ function getRelativeLuminance(color) {
104
+ const red = normalizeColorChannel(color.red);
105
+ const green = normalizeColorChannel(color.green);
106
+ const blue = normalizeColorChannel(color.blue);
107
+ return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
108
+ }
109
+ function normalizeColorChannel(value) {
110
+ const normalized = value / 255;
111
+ if (normalized <= 0.03928)
112
+ return normalized / 12.92;
113
+ return ((normalized + 0.055) / 1.055) ** 2.4;
114
+ }
20
115
  //# sourceMappingURL=colors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"colors.js","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"AA6CA,MAAM,UAAU,yBAAyB,CACvC,KAA8B,EAC9B,YAAoC,EAAE;IAEtC,OAAO;QACL,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI;QAC5D,cAAc,EAAE,SAAS,CAAC,cAAc,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM;QAC1E,UAAU,EAAE,SAAS,CAAC,UAAU,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM;QAClE,WAAW,EAAE,SAAS,CAAC,WAAW,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO;QACrE,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO;QAC/D,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK;QAC/D,WAAW,EAAE,SAAS,CAAC,WAAW,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI;QAClE,UAAU,EAAE,SAAS,CAAC,UAAU,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO;QACnE,kBAAkB,EAAE,SAAS,CAAC,kBAAkB,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI;QACvF,WAAW,EAAE,SAAS,CAAC,WAAW,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO;QACrE,WAAW,EAAE,SAAS,CAAC,WAAW,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI;QAClE,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM;QACvE,gBAAgB,EAAE,SAAS,CAAC,gBAAgB,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO;QAC/E,YAAY,EAAE,SAAS,CAAC,YAAY,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM;QACtE,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO;KAClE,CAAC;AACJ,CAAC","sourcesContent":["export interface TabletopColorScheme {\n readonly cardBack: string;\n readonly cardBackBorder: string;\n readonly cardBorder: string;\n readonly cardSurface: string;\n readonly cardText: string;\n readonly mutedText: string;\n readonly redSuitText: string;\n readonly seatBorder: string;\n readonly seatSelectedBorder: string;\n readonly seatSurface: string;\n readonly tableBorder: string;\n readonly tableFelt: string;\n readonly tableInnerBorder: string;\n readonly tokenSurface: string;\n readonly tokenText: string;\n}\n\nexport type TabletopColorOverrides = Partial<TabletopColorScheme>;\n\nexport interface TabletopColorThemeShape {\n semantics: {\n action: {\n primary: {\n base: string;\n softBg: string;\n };\n };\n content: {\n default: string;\n muted: string;\n };\n neutral: {\n border: string;\n divider: string;\n surface: string;\n surfaceHover: string;\n };\n warning: {\n base: string;\n softBg: string;\n };\n };\n}\n\nexport function createTabletopColorScheme(\n theme: TabletopColorThemeShape,\n overrides: TabletopColorOverrides = {},\n): TabletopColorScheme {\n return {\n cardBack: overrides.cardBack ?? theme.semantics.warning.base,\n cardBackBorder: overrides.cardBackBorder ?? theme.semantics.neutral.border,\n cardBorder: overrides.cardBorder ?? theme.semantics.neutral.border,\n cardSurface: overrides.cardSurface ?? theme.semantics.neutral.surface,\n cardText: overrides.cardText ?? theme.semantics.content.default,\n mutedText: overrides.mutedText ?? theme.semantics.content.muted,\n redSuitText: overrides.redSuitText ?? theme.semantics.warning.base,\n seatBorder: overrides.seatBorder ?? theme.semantics.neutral.divider,\n seatSelectedBorder: overrides.seatSelectedBorder ?? theme.semantics.action.primary.base,\n seatSurface: overrides.seatSurface ?? theme.semantics.neutral.surface,\n tableBorder: overrides.tableBorder ?? theme.semantics.warning.base,\n tableFelt: overrides.tableFelt ?? theme.semantics.action.primary.softBg,\n tableInnerBorder: overrides.tableInnerBorder ?? theme.semantics.neutral.divider,\n tokenSurface: overrides.tokenSurface ?? theme.semantics.warning.softBg,\n tokenText: overrides.tokenText ?? theme.semantics.content.default,\n };\n}\n"]}
1
+ {"version":3,"file":"colors.js","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"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","sourcesContent":["export interface TabletopColorScheme {\n readonly cardBack: string;\n readonly cardBackBorder: string;\n readonly cardBorder: string;\n readonly cardSurface: string;\n readonly cardText: string;\n readonly mutedText: string;\n readonly redSuitText: string;\n readonly seatBorder: string;\n readonly seatMutedText: string;\n readonly seatSelectedBorder: string;\n readonly seatSurface: string;\n readonly seatText: string;\n readonly tableBorder: string;\n readonly tableFelt: string;\n readonly tableInnerBorder: string;\n readonly tableMutedText: string;\n readonly tableText: string;\n readonly tokenSurface: string;\n readonly tokenText: string;\n}\n\nexport type TabletopColorOverrides = Partial<TabletopColorScheme>;\n\nexport interface TabletopColorThemeShape {\n semantics: {\n action: {\n primary: {\n base: string;\n softBg: string;\n };\n };\n content: {\n default: string;\n muted: string;\n };\n neutral: {\n border: string;\n divider: string;\n surface: string;\n surfaceHover: string;\n };\n warning: {\n base: string;\n softBg: string;\n };\n };\n}\n\ninterface RgbColor {\n readonly red: number;\n readonly green: number;\n readonly blue: number;\n}\n\nconst DARK_TEXT_FALLBACK = '#111827';\nconst LIGHT_TEXT_FALLBACK = '#ffffff';\nconst MINIMUM_READABLE_CONTRAST = 4.5;\n\nexport function createTabletopColorScheme(\n theme: TabletopColorThemeShape,\n overrides: TabletopColorOverrides = {},\n): TabletopColorScheme {\n const cardSurface = overrides.cardSurface ?? theme.semantics.neutral.surface;\n const seatSurface = overrides.seatSurface ?? theme.semantics.neutral.surface;\n const tableFelt = overrides.tableFelt ?? theme.semantics.action.primary.softBg;\n const tokenSurface = overrides.tokenSurface ?? theme.semantics.warning.softBg;\n\n const cardText =\n overrides.cardText ??\n resolveReadableTextColor(cardSurface, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);\n const seatText =\n overrides.seatText ??\n resolveReadableTextColor(seatSurface, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);\n const tableText =\n overrides.tableText ??\n resolveReadableTextColor(tableFelt, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);\n const tokenText =\n overrides.tokenText ??\n resolveReadableTextColor(tokenSurface, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);\n\n return {\n cardBack: overrides.cardBack ?? theme.semantics.warning.base,\n cardBackBorder: overrides.cardBackBorder ?? theme.semantics.neutral.border,\n cardBorder: overrides.cardBorder ?? theme.semantics.neutral.border,\n cardSurface,\n cardText,\n mutedText:\n overrides.mutedText ??\n resolveReadableTextColor(cardSurface, theme.semantics.content.muted, cardText),\n redSuitText:\n overrides.redSuitText ??\n resolveReadableTextColor(cardSurface, theme.semantics.warning.base, cardText),\n seatBorder: overrides.seatBorder ?? theme.semantics.neutral.divider,\n seatMutedText:\n overrides.seatMutedText ??\n resolveReadableTextColor(seatSurface, theme.semantics.content.muted, seatText),\n seatSelectedBorder: overrides.seatSelectedBorder ?? theme.semantics.action.primary.base,\n seatSurface,\n seatText,\n tableBorder: overrides.tableBorder ?? theme.semantics.warning.base,\n tableFelt,\n tableInnerBorder: overrides.tableInnerBorder ?? theme.semantics.neutral.divider,\n tableMutedText:\n overrides.tableMutedText ??\n resolveReadableTextColor(tableFelt, theme.semantics.content.muted, tableText),\n tableText,\n tokenSurface,\n tokenText,\n };\n}\n\nfunction resolveReadableTextColor(background: string, preferred: string, fallback: string): string {\n const preferredContrast = getContrastRatio(background, preferred);\n if (preferredContrast !== null && preferredContrast >= MINIMUM_READABLE_CONTRAST) {\n return preferred;\n }\n\n const candidates = uniqueColors([preferred, fallback, LIGHT_TEXT_FALLBACK, DARK_TEXT_FALLBACK]);\n const [readableCandidate] = candidates\n .map((candidate) => ({ candidate, contrast: getContrastRatio(background, candidate) }))\n .filter(\n (entry): entry is { readonly candidate: string; readonly contrast: number } =>\n entry.contrast !== null,\n )\n .sort((left, right) => right.contrast - left.contrast);\n\n return readableCandidate?.candidate ?? preferred;\n}\n\nfunction uniqueColors(colors: readonly string[]): string[] {\n return [...new Set(colors)];\n}\n\nfunction getContrastRatio(background: string, foreground: string): number | null {\n const backgroundColor = parseHexColor(background);\n const foregroundColor = parseHexColor(foreground);\n\n if (backgroundColor === null || foregroundColor === null) return null;\n\n const backgroundLuminance = getRelativeLuminance(backgroundColor);\n const foregroundLuminance = getRelativeLuminance(foregroundColor);\n const lighter = Math.max(backgroundLuminance, foregroundLuminance);\n const darker = Math.min(backgroundLuminance, foregroundLuminance);\n\n return (lighter + 0.05) / (darker + 0.05);\n}\n\nfunction parseHexColor(color: string): RgbColor | null {\n const value = color.trim();\n const hex = value.startsWith('#') ? value.slice(1) : '';\n if (!isSupportedHexLength(hex) || !isHexColorValue(hex)) return null;\n\n if (hex.length === 3) {\n const red = hex.slice(0, 1);\n const green = hex.slice(1, 2);\n const blue = hex.slice(2, 3);\n\n return {\n red: parseInt(red + red, 16),\n green: parseInt(green + green, 16),\n blue: parseInt(blue + blue, 16),\n };\n }\n\n return {\n red: parseInt(hex.slice(0, 2), 16),\n green: parseInt(hex.slice(2, 4), 16),\n blue: parseInt(hex.slice(4, 6), 16),\n };\n}\n\nfunction isSupportedHexLength(hex: string): boolean {\n return hex.length === 3 || hex.length === 6 || hex.length === 8;\n}\n\nfunction isHexColorValue(hex: string): boolean {\n return [...hex].every((character) => isHexCharacter(character));\n}\n\nfunction isHexCharacter(character: string): boolean {\n const code = character.charCodeAt(0);\n const isDigit = code >= 48 && code <= 57;\n const isUpperHex = code >= 65 && code <= 70;\n const isLowerHex = code >= 97 && code <= 102;\n\n return isDigit || isUpperHex || isLowerHex;\n}\n\nfunction getRelativeLuminance(color: RgbColor): number {\n const red = normalizeColorChannel(color.red);\n const green = normalizeColorChannel(color.green);\n const blue = normalizeColorChannel(color.blue);\n\n return 0.2126 * red + 0.7152 * green + 0.0722 * blue;\n}\n\nfunction normalizeColorChannel(value: number): number {\n const normalized = value / 255;\n if (normalized <= 0.03928) return normalized / 12.92;\n return ((normalized + 0.055) / 1.055) ** 2.4;\n}\n"]}
@@ -43,11 +43,11 @@ export function TabletopTable({ seats, centerCards = [], centerLabel, centerSubl
43
43
  <View style={styles.centerContent}>
44
44
  {centerCards.length > 0 ? (<CardHand cards={centerCards} colorScheme={colorScheme} size={cardSize}/>) : null}
45
45
  {centerLabel !== undefined ? (<View style={[styles.centerLabel, { backgroundColor: colors.seatSurface }]}>
46
- <Text style={[styles.centerLabelText, { color: colors.cardText }]}>
46
+ <Text style={[styles.centerLabelText, { color: colors.seatText }]}>
47
47
  {centerLabel}
48
48
  </Text>
49
49
  </View>) : null}
50
- {centerSublabel !== undefined ? (<Text style={[styles.centerSublabel, { color: colors.mutedText }]}>
50
+ {centerSublabel !== undefined ? (<Text style={[styles.centerSublabel, { color: colors.tableMutedText }]}>
51
51
  {centerSublabel}
52
52
  </Text>) : null}
53
53
  </View>
@@ -66,8 +66,8 @@ export function TabletopTable({ seats, centerCards = [], centerLabel, centerSubl
66
66
  },
67
67
  ]} testID={testID ? `${testID}-seat-${seat.id}` : undefined}>
68
68
  {seat.cards !== undefined || seat.faceDownCards !== undefined ? (<CardHand cards={seat.cards} colorScheme={colorScheme} faceDownCards={seat.faceDownCards} muted={seat.muted ?? seat.disabled} size={cardSize}/>) : null}
69
- <Text style={[styles.seatLabel, { color: colors.cardText }]}>{seat.label}</Text>
70
- {seat.sublabel !== undefined ? (<Text style={[styles.seatSublabel, { color: colors.mutedText }]}>
69
+ <Text style={[styles.seatLabel, { color: colors.seatText }]}>{seat.label}</Text>
70
+ {seat.sublabel !== undefined ? (<Text style={[styles.seatSublabel, { color: colors.seatMutedText }]}>
71
71
  {seat.sublabel}
72
72
  </Text>) : null}
73
73
  {seat.tokenLabel !== undefined ? (<View style={[styles.token, { backgroundColor: colors.tokenSurface }]}>
@@ -99,7 +99,7 @@ const styles = StyleSheet.create({
99
99
  },
100
100
  centerSublabel: {
101
101
  fontSize: 11,
102
- fontWeight: '600',
102
+ fontWeight: '700',
103
103
  },
104
104
  circleSurface: {
105
105
  borderRadius: 999,
@@ -142,7 +142,7 @@ const styles = StyleSheet.create({
142
142
  },
143
143
  seatSublabel: {
144
144
  fontSize: 10,
145
- fontWeight: '600',
145
+ fontWeight: '700',
146
146
  },
147
147
  surface: {
148
148
  borderWidth: 8,
@@ -1 +1 @@
1
- {"version":3,"file":"TabletopTable.js","sourceRoot":"","sources":["../../../src/components/tabletop-table/TabletopTable.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useZoraTheme } from '@ankhorage/zora';\nimport React from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\n\nimport { createTabletopColorScheme } from '../../colors';\nimport { getTabletopSeatPosition } from '../../tablePositions';\nimport type { TabletopSeatState } from '../../types';\nimport { CardHand } from '../card-hand';\nimport type { TabletopTableProps } from './types';\n\nfunction createSeatAccessibilityLabel(seat: TabletopSeatState): string | undefined {\n if (seat.accessibilityLabel !== undefined) return seat.accessibilityLabel;\n if (typeof seat.label !== 'string') return undefined;\n\n const parts = [seat.label];\n if (typeof seat.sublabel === 'string') parts.push(seat.sublabel);\n if (typeof seat.tokenLabel === 'string') parts.push(seat.tokenLabel);\n if (seat.selected) parts.push('selected');\n if (seat.muted) parts.push('muted');\n\n return parts.join(', ');\n}\n\nexport function TabletopTable({\n seats,\n centerCards = [],\n centerLabel,\n centerSublabel,\n shape = 'oval',\n seatCount,\n cardSize = 'small',\n disabled = false,\n colorScheme,\n accessibilityLabel,\n testID,\n}: TabletopTableProps) {\n const { theme } = useZoraTheme();\n const colors = React.useMemo(\n () => createTabletopColorScheme(theme, colorScheme),\n [colorScheme, theme],\n );\n const resolvedSeatCount = seatCount ?? seats.length;\n\n return (\n <View\n accessibilityLabel={accessibilityLabel}\n style={[styles.root, { opacity: disabled ? 0.56 : 1 }]}\n testID={testID}\n >\n <View\n style={[\n styles.surface,\n shape === 'circle' ? styles.circleSurface : styles.ovalSurface,\n shape === 'rounded' ? styles.roundedSurface : null,\n {\n backgroundColor: colors.tableFelt,\n borderColor: colors.tableBorder,\n },\n ]}\n >\n <View\n pointerEvents=\"none\"\n style={[\n styles.innerSurface,\n shape === 'circle' ? styles.circleSurface : styles.ovalSurface,\n shape === 'rounded' ? styles.roundedSurface : null,\n { borderColor: colors.tableInnerBorder },\n ]}\n />\n <View style={styles.centerContent}>\n {centerCards.length > 0 ? (\n <CardHand cards={centerCards} colorScheme={colorScheme} size={cardSize} />\n ) : null}\n {centerLabel !== undefined ? (\n <View style={[styles.centerLabel, { backgroundColor: colors.seatSurface }]}>\n <Text style={[styles.centerLabelText, { color: colors.cardText }]}>\n {centerLabel}\n </Text>\n </View>\n ) : null}\n {centerSublabel !== undefined ? (\n <Text style={[styles.centerSublabel, { color: colors.mutedText }]}>\n {centerSublabel}\n </Text>\n ) : null}\n </View>\n </View>\n\n {seats.map((seat, index) => {\n const position = getTabletopSeatPosition(index, resolvedSeatCount);\n return (\n <View\n accessibilityLabel={createSeatAccessibilityLabel(seat)}\n accessibilityRole=\"summary\"\n key={seat.id}\n style={[\n styles.seat,\n {\n borderColor: seat.selected ? colors.seatSelectedBorder : colors.seatBorder,\n backgroundColor: colors.seatSurface,\n left: position.left,\n opacity: seat.muted ? 0.48 : 1,\n top: position.top,\n },\n ]}\n testID={testID ? `${testID}-seat-${seat.id}` : undefined}\n >\n {seat.cards !== undefined || seat.faceDownCards !== undefined ? (\n <CardHand\n cards={seat.cards}\n colorScheme={colorScheme}\n faceDownCards={seat.faceDownCards}\n muted={seat.muted ?? seat.disabled}\n size={cardSize}\n />\n ) : null}\n <Text style={[styles.seatLabel, { color: colors.cardText }]}>{seat.label}</Text>\n {seat.sublabel !== undefined ? (\n <Text style={[styles.seatSublabel, { color: colors.mutedText }]}>\n {seat.sublabel}\n </Text>\n ) : null}\n {seat.tokenLabel !== undefined ? (\n <View style={[styles.token, { backgroundColor: colors.tokenSurface }]}>\n <Text style={[styles.tokenText, { color: colors.tokenText }]}>\n {seat.tokenLabel}\n </Text>\n </View>\n ) : null}\n </View>\n );\n })}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n centerContent: {\n alignItems: 'center',\n gap: 6,\n left: 0,\n position: 'absolute',\n right: 0,\n top: '35%',\n },\n centerLabel: {\n borderRadius: 999,\n paddingHorizontal: 10,\n paddingVertical: 4,\n },\n centerLabelText: {\n fontSize: 12,\n fontWeight: '800',\n },\n centerSublabel: {\n fontSize: 11,\n fontWeight: '600',\n },\n circleSurface: {\n borderRadius: 999,\n },\n innerSurface: {\n borderWidth: 1,\n bottom: 10,\n left: 10,\n opacity: 0.44,\n position: 'absolute',\n right: 10,\n top: 10,\n },\n ovalSurface: {\n borderRadius: 999,\n },\n root: {\n aspectRatio: 1.6,\n maxWidth: 760,\n position: 'relative',\n width: '100%',\n },\n roundedSurface: {\n borderRadius: 28,\n },\n seat: {\n alignItems: 'center',\n borderRadius: 10,\n borderWidth: 1,\n gap: 3,\n minWidth: 64,\n paddingHorizontal: 6,\n paddingVertical: 5,\n position: 'absolute',\n transform: [{ translateX: -32 }, { translateY: -28 }],\n },\n seatLabel: {\n fontSize: 11,\n fontWeight: '800',\n },\n seatSublabel: {\n fontSize: 10,\n fontWeight: '600',\n },\n surface: {\n borderWidth: 8,\n bottom: 28,\n left: 24,\n position: 'absolute',\n right: 24,\n top: 24,\n },\n token: {\n borderRadius: 999,\n paddingHorizontal: 6,\n paddingVertical: 2,\n },\n tokenText: {\n fontSize: 10,\n fontWeight: '800',\n },\n});\n"]}
1
+ {"version":3,"file":"TabletopTable.js","sourceRoot":"","sources":["../../../src/components/tabletop-table/TabletopTable.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useZoraTheme } from '@ankhorage/zora';\nimport React from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\n\nimport { createTabletopColorScheme } from '../../colors';\nimport { getTabletopSeatPosition } from '../../tablePositions';\nimport type { TabletopSeatState } from '../../types';\nimport { CardHand } from '../card-hand';\nimport type { TabletopTableProps } from './types';\n\nfunction createSeatAccessibilityLabel(seat: TabletopSeatState): string | undefined {\n if (seat.accessibilityLabel !== undefined) return seat.accessibilityLabel;\n if (typeof seat.label !== 'string') return undefined;\n\n const parts = [seat.label];\n if (typeof seat.sublabel === 'string') parts.push(seat.sublabel);\n if (typeof seat.tokenLabel === 'string') parts.push(seat.tokenLabel);\n if (seat.selected) parts.push('selected');\n if (seat.muted) parts.push('muted');\n\n return parts.join(', ');\n}\n\nexport function TabletopTable({\n seats,\n centerCards = [],\n centerLabel,\n centerSublabel,\n shape = 'oval',\n seatCount,\n cardSize = 'small',\n disabled = false,\n colorScheme,\n accessibilityLabel,\n testID,\n}: TabletopTableProps) {\n const { theme } = useZoraTheme();\n const colors = React.useMemo(\n () => createTabletopColorScheme(theme, colorScheme),\n [colorScheme, theme],\n );\n const resolvedSeatCount = seatCount ?? seats.length;\n\n return (\n <View\n accessibilityLabel={accessibilityLabel}\n style={[styles.root, { opacity: disabled ? 0.56 : 1 }]}\n testID={testID}\n >\n <View\n style={[\n styles.surface,\n shape === 'circle' ? styles.circleSurface : styles.ovalSurface,\n shape === 'rounded' ? styles.roundedSurface : null,\n {\n backgroundColor: colors.tableFelt,\n borderColor: colors.tableBorder,\n },\n ]}\n >\n <View\n pointerEvents=\"none\"\n style={[\n styles.innerSurface,\n shape === 'circle' ? styles.circleSurface : styles.ovalSurface,\n shape === 'rounded' ? styles.roundedSurface : null,\n { borderColor: colors.tableInnerBorder },\n ]}\n />\n <View style={styles.centerContent}>\n {centerCards.length > 0 ? (\n <CardHand cards={centerCards} colorScheme={colorScheme} size={cardSize} />\n ) : null}\n {centerLabel !== undefined ? (\n <View style={[styles.centerLabel, { backgroundColor: colors.seatSurface }]}>\n <Text style={[styles.centerLabelText, { color: colors.seatText }]}>\n {centerLabel}\n </Text>\n </View>\n ) : null}\n {centerSublabel !== undefined ? (\n <Text style={[styles.centerSublabel, { color: colors.tableMutedText }]}>\n {centerSublabel}\n </Text>\n ) : null}\n </View>\n </View>\n\n {seats.map((seat, index) => {\n const position = getTabletopSeatPosition(index, resolvedSeatCount);\n return (\n <View\n accessibilityLabel={createSeatAccessibilityLabel(seat)}\n accessibilityRole=\"summary\"\n key={seat.id}\n style={[\n styles.seat,\n {\n borderColor: seat.selected ? colors.seatSelectedBorder : colors.seatBorder,\n backgroundColor: colors.seatSurface,\n left: position.left,\n opacity: seat.muted ? 0.48 : 1,\n top: position.top,\n },\n ]}\n testID={testID ? `${testID}-seat-${seat.id}` : undefined}\n >\n {seat.cards !== undefined || seat.faceDownCards !== undefined ? (\n <CardHand\n cards={seat.cards}\n colorScheme={colorScheme}\n faceDownCards={seat.faceDownCards}\n muted={seat.muted ?? seat.disabled}\n size={cardSize}\n />\n ) : null}\n <Text style={[styles.seatLabel, { color: colors.seatText }]}>{seat.label}</Text>\n {seat.sublabel !== undefined ? (\n <Text style={[styles.seatSublabel, { color: colors.seatMutedText }]}>\n {seat.sublabel}\n </Text>\n ) : null}\n {seat.tokenLabel !== undefined ? (\n <View style={[styles.token, { backgroundColor: colors.tokenSurface }]}>\n <Text style={[styles.tokenText, { color: colors.tokenText }]}>\n {seat.tokenLabel}\n </Text>\n </View>\n ) : null}\n </View>\n );\n })}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n centerContent: {\n alignItems: 'center',\n gap: 6,\n left: 0,\n position: 'absolute',\n right: 0,\n top: '35%',\n },\n centerLabel: {\n borderRadius: 999,\n paddingHorizontal: 10,\n paddingVertical: 4,\n },\n centerLabelText: {\n fontSize: 12,\n fontWeight: '800',\n },\n centerSublabel: {\n fontSize: 11,\n fontWeight: '700',\n },\n circleSurface: {\n borderRadius: 999,\n },\n innerSurface: {\n borderWidth: 1,\n bottom: 10,\n left: 10,\n opacity: 0.44,\n position: 'absolute',\n right: 10,\n top: 10,\n },\n ovalSurface: {\n borderRadius: 999,\n },\n root: {\n aspectRatio: 1.6,\n maxWidth: 760,\n position: 'relative',\n width: '100%',\n },\n roundedSurface: {\n borderRadius: 28,\n },\n seat: {\n alignItems: 'center',\n borderRadius: 10,\n borderWidth: 1,\n gap: 3,\n minWidth: 64,\n paddingHorizontal: 6,\n paddingVertical: 5,\n position: 'absolute',\n transform: [{ translateX: -32 }, { translateY: -28 }],\n },\n seatLabel: {\n fontSize: 11,\n fontWeight: '800',\n },\n seatSublabel: {\n fontSize: 10,\n fontWeight: '700',\n },\n surface: {\n borderWidth: 8,\n bottom: 28,\n left: 24,\n position: 'absolute',\n right: 24,\n top: 24,\n },\n token: {\n borderRadius: 999,\n paddingHorizontal: 6,\n paddingVertical: 2,\n },\n tokenText: {\n fontSize: 10,\n fontWeight: '800',\n },\n});\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/zora-tabletop",
3
3
  "type": "module",
4
- "version": "0.0.0",
4
+ "version": "0.0.2",
5
5
  "description": "Tabletop and card-game UI primitives for React Native and React Native Web, built on ZORA.",
6
6
  "homepage": "https://github.com/ankhorage/zora-tabletop#readme",
7
7
  "bugs": {
@@ -0,0 +1,58 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { createTabletopColorScheme, type TabletopColorThemeShape } from './colors';
4
+
5
+ const baseTheme = {
6
+ semantics: {
7
+ action: {
8
+ primary: {
9
+ base: '#047857',
10
+ softBg: '#047857',
11
+ },
12
+ },
13
+ content: {
14
+ default: '#047857',
15
+ muted: '#059669',
16
+ },
17
+ neutral: {
18
+ border: '#d1d5db',
19
+ divider: '#e5e7eb',
20
+ surface: '#ffffff',
21
+ surfaceHover: '#f9fafb',
22
+ },
23
+ warning: {
24
+ base: '#b45309',
25
+ softBg: '#fef3c7',
26
+ },
27
+ },
28
+ } satisfies TabletopColorThemeShape;
29
+
30
+ describe('createTabletopColorScheme', () => {
31
+ test('derives readable table text when preferred theme text matches the table surface', () => {
32
+ const colors = createTabletopColorScheme(baseTheme);
33
+
34
+ expect(colors.tableFelt).toBe('#047857');
35
+ expect(colors.tableText).toBe('#ffffff');
36
+ expect(colors.tableMutedText).toBe('#ffffff');
37
+ });
38
+
39
+ test('derives readable seat text for dark custom seat surfaces', () => {
40
+ const colors = createTabletopColorScheme(baseTheme, {
41
+ seatSurface: '#064e3b',
42
+ });
43
+
44
+ expect(colors.seatText).toBe('#ffffff');
45
+ expect(colors.seatMutedText).toBe('#ffffff');
46
+ });
47
+
48
+ test('keeps explicit readable text overrides', () => {
49
+ const colors = createTabletopColorScheme(baseTheme, {
50
+ seatSurface: '#064e3b',
51
+ seatText: '#fef3c7',
52
+ tableText: '#fef3c7',
53
+ });
54
+
55
+ expect(colors.seatText).toBe('#fef3c7');
56
+ expect(colors.tableText).toBe('#fef3c7');
57
+ });
58
+ });
package/src/colors.ts CHANGED
@@ -7,11 +7,15 @@ export interface TabletopColorScheme {
7
7
  readonly mutedText: string;
8
8
  readonly redSuitText: string;
9
9
  readonly seatBorder: string;
10
+ readonly seatMutedText: string;
10
11
  readonly seatSelectedBorder: string;
11
12
  readonly seatSurface: string;
13
+ readonly seatText: string;
12
14
  readonly tableBorder: string;
13
15
  readonly tableFelt: string;
14
16
  readonly tableInnerBorder: string;
17
+ readonly tableMutedText: string;
18
+ readonly tableText: string;
15
19
  readonly tokenSurface: string;
16
20
  readonly tokenText: string;
17
21
  }
@@ -43,25 +47,156 @@ export interface TabletopColorThemeShape {
43
47
  };
44
48
  }
45
49
 
50
+ interface RgbColor {
51
+ readonly red: number;
52
+ readonly green: number;
53
+ readonly blue: number;
54
+ }
55
+
56
+ const DARK_TEXT_FALLBACK = '#111827';
57
+ const LIGHT_TEXT_FALLBACK = '#ffffff';
58
+ const MINIMUM_READABLE_CONTRAST = 4.5;
59
+
46
60
  export function createTabletopColorScheme(
47
61
  theme: TabletopColorThemeShape,
48
62
  overrides: TabletopColorOverrides = {},
49
63
  ): TabletopColorScheme {
64
+ const cardSurface = overrides.cardSurface ?? theme.semantics.neutral.surface;
65
+ const seatSurface = overrides.seatSurface ?? theme.semantics.neutral.surface;
66
+ const tableFelt = overrides.tableFelt ?? theme.semantics.action.primary.softBg;
67
+ const tokenSurface = overrides.tokenSurface ?? theme.semantics.warning.softBg;
68
+
69
+ const cardText =
70
+ overrides.cardText ??
71
+ resolveReadableTextColor(cardSurface, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);
72
+ const seatText =
73
+ overrides.seatText ??
74
+ resolveReadableTextColor(seatSurface, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);
75
+ const tableText =
76
+ overrides.tableText ??
77
+ resolveReadableTextColor(tableFelt, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);
78
+ const tokenText =
79
+ overrides.tokenText ??
80
+ resolveReadableTextColor(tokenSurface, theme.semantics.content.default, LIGHT_TEXT_FALLBACK);
81
+
50
82
  return {
51
83
  cardBack: overrides.cardBack ?? theme.semantics.warning.base,
52
84
  cardBackBorder: overrides.cardBackBorder ?? theme.semantics.neutral.border,
53
85
  cardBorder: overrides.cardBorder ?? theme.semantics.neutral.border,
54
- cardSurface: overrides.cardSurface ?? theme.semantics.neutral.surface,
55
- cardText: overrides.cardText ?? theme.semantics.content.default,
56
- mutedText: overrides.mutedText ?? theme.semantics.content.muted,
57
- redSuitText: overrides.redSuitText ?? theme.semantics.warning.base,
86
+ cardSurface,
87
+ cardText,
88
+ mutedText:
89
+ overrides.mutedText ??
90
+ resolveReadableTextColor(cardSurface, theme.semantics.content.muted, cardText),
91
+ redSuitText:
92
+ overrides.redSuitText ??
93
+ resolveReadableTextColor(cardSurface, theme.semantics.warning.base, cardText),
58
94
  seatBorder: overrides.seatBorder ?? theme.semantics.neutral.divider,
95
+ seatMutedText:
96
+ overrides.seatMutedText ??
97
+ resolveReadableTextColor(seatSurface, theme.semantics.content.muted, seatText),
59
98
  seatSelectedBorder: overrides.seatSelectedBorder ?? theme.semantics.action.primary.base,
60
- seatSurface: overrides.seatSurface ?? theme.semantics.neutral.surface,
99
+ seatSurface,
100
+ seatText,
61
101
  tableBorder: overrides.tableBorder ?? theme.semantics.warning.base,
62
- tableFelt: overrides.tableFelt ?? theme.semantics.action.primary.softBg,
102
+ tableFelt,
63
103
  tableInnerBorder: overrides.tableInnerBorder ?? theme.semantics.neutral.divider,
64
- tokenSurface: overrides.tokenSurface ?? theme.semantics.warning.softBg,
65
- tokenText: overrides.tokenText ?? theme.semantics.content.default,
104
+ tableMutedText:
105
+ overrides.tableMutedText ??
106
+ resolveReadableTextColor(tableFelt, theme.semantics.content.muted, tableText),
107
+ tableText,
108
+ tokenSurface,
109
+ tokenText,
110
+ };
111
+ }
112
+
113
+ function resolveReadableTextColor(background: string, preferred: string, fallback: string): string {
114
+ const preferredContrast = getContrastRatio(background, preferred);
115
+ if (preferredContrast !== null && preferredContrast >= MINIMUM_READABLE_CONTRAST) {
116
+ return preferred;
117
+ }
118
+
119
+ const candidates = uniqueColors([preferred, fallback, LIGHT_TEXT_FALLBACK, DARK_TEXT_FALLBACK]);
120
+ const [readableCandidate] = candidates
121
+ .map((candidate) => ({ candidate, contrast: getContrastRatio(background, candidate) }))
122
+ .filter(
123
+ (entry): entry is { readonly candidate: string; readonly contrast: number } =>
124
+ entry.contrast !== null,
125
+ )
126
+ .sort((left, right) => right.contrast - left.contrast);
127
+
128
+ return readableCandidate?.candidate ?? preferred;
129
+ }
130
+
131
+ function uniqueColors(colors: readonly string[]): string[] {
132
+ return [...new Set(colors)];
133
+ }
134
+
135
+ function getContrastRatio(background: string, foreground: string): number | null {
136
+ const backgroundColor = parseHexColor(background);
137
+ const foregroundColor = parseHexColor(foreground);
138
+
139
+ if (backgroundColor === null || foregroundColor === null) return null;
140
+
141
+ const backgroundLuminance = getRelativeLuminance(backgroundColor);
142
+ const foregroundLuminance = getRelativeLuminance(foregroundColor);
143
+ const lighter = Math.max(backgroundLuminance, foregroundLuminance);
144
+ const darker = Math.min(backgroundLuminance, foregroundLuminance);
145
+
146
+ return (lighter + 0.05) / (darker + 0.05);
147
+ }
148
+
149
+ function parseHexColor(color: string): RgbColor | null {
150
+ const value = color.trim();
151
+ const hex = value.startsWith('#') ? value.slice(1) : '';
152
+ if (!isSupportedHexLength(hex) || !isHexColorValue(hex)) return null;
153
+
154
+ if (hex.length === 3) {
155
+ const red = hex.slice(0, 1);
156
+ const green = hex.slice(1, 2);
157
+ const blue = hex.slice(2, 3);
158
+
159
+ return {
160
+ red: parseInt(red + red, 16),
161
+ green: parseInt(green + green, 16),
162
+ blue: parseInt(blue + blue, 16),
163
+ };
164
+ }
165
+
166
+ return {
167
+ red: parseInt(hex.slice(0, 2), 16),
168
+ green: parseInt(hex.slice(2, 4), 16),
169
+ blue: parseInt(hex.slice(4, 6), 16),
66
170
  };
67
171
  }
172
+
173
+ function isSupportedHexLength(hex: string): boolean {
174
+ return hex.length === 3 || hex.length === 6 || hex.length === 8;
175
+ }
176
+
177
+ function isHexColorValue(hex: string): boolean {
178
+ return [...hex].every((character) => isHexCharacter(character));
179
+ }
180
+
181
+ function isHexCharacter(character: string): boolean {
182
+ const code = character.charCodeAt(0);
183
+ const isDigit = code >= 48 && code <= 57;
184
+ const isUpperHex = code >= 65 && code <= 70;
185
+ const isLowerHex = code >= 97 && code <= 102;
186
+
187
+ return isDigit || isUpperHex || isLowerHex;
188
+ }
189
+
190
+ function getRelativeLuminance(color: RgbColor): number {
191
+ const red = normalizeColorChannel(color.red);
192
+ const green = normalizeColorChannel(color.green);
193
+ const blue = normalizeColorChannel(color.blue);
194
+
195
+ return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
196
+ }
197
+
198
+ function normalizeColorChannel(value: number): number {
199
+ const normalized = value / 255;
200
+ if (normalized <= 0.03928) return normalized / 12.92;
201
+ return ((normalized + 0.055) / 1.055) ** 2.4;
202
+ }
@@ -73,13 +73,13 @@ export function TabletopTable({
73
73
  ) : null}
74
74
  {centerLabel !== undefined ? (
75
75
  <View style={[styles.centerLabel, { backgroundColor: colors.seatSurface }]}>
76
- <Text style={[styles.centerLabelText, { color: colors.cardText }]}>
76
+ <Text style={[styles.centerLabelText, { color: colors.seatText }]}>
77
77
  {centerLabel}
78
78
  </Text>
79
79
  </View>
80
80
  ) : null}
81
81
  {centerSublabel !== undefined ? (
82
- <Text style={[styles.centerSublabel, { color: colors.mutedText }]}>
82
+ <Text style={[styles.centerSublabel, { color: colors.tableMutedText }]}>
83
83
  {centerSublabel}
84
84
  </Text>
85
85
  ) : null}
@@ -114,9 +114,9 @@ export function TabletopTable({
114
114
  size={cardSize}
115
115
  />
116
116
  ) : null}
117
- <Text style={[styles.seatLabel, { color: colors.cardText }]}>{seat.label}</Text>
117
+ <Text style={[styles.seatLabel, { color: colors.seatText }]}>{seat.label}</Text>
118
118
  {seat.sublabel !== undefined ? (
119
- <Text style={[styles.seatSublabel, { color: colors.mutedText }]}>
119
+ <Text style={[styles.seatSublabel, { color: colors.seatMutedText }]}>
120
120
  {seat.sublabel}
121
121
  </Text>
122
122
  ) : null}
@@ -154,7 +154,7 @@ const styles = StyleSheet.create({
154
154
  },
155
155
  centerSublabel: {
156
156
  fontSize: 11,
157
- fontWeight: '600',
157
+ fontWeight: '700',
158
158
  },
159
159
  circleSurface: {
160
160
  borderRadius: 999,
@@ -197,7 +197,7 @@ const styles = StyleSheet.create({
197
197
  },
198
198
  seatSublabel: {
199
199
  fontSize: 10,
200
- fontWeight: '600',
200
+ fontWeight: '700',
201
201
  },
202
202
  surface: {
203
203
  borderWidth: 8,