@yenio-pld/blocks 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +100 -2
  2. package/dist/avatar.cjs +88 -0
  3. package/dist/avatar.cjs.map +1 -0
  4. package/dist/avatar.d.cts +18 -0
  5. package/dist/avatar.d.ts +18 -0
  6. package/dist/avatar.js +84 -0
  7. package/dist/avatar.js.map +1 -0
  8. package/dist/consent.cjs +85 -0
  9. package/dist/consent.cjs.map +1 -0
  10. package/dist/consent.d.cts +20 -0
  11. package/dist/consent.d.ts +20 -0
  12. package/dist/consent.js +73 -0
  13. package/dist/consent.js.map +1 -0
  14. package/dist/index.cjs +4053 -0
  15. package/dist/index.cjs.map +1 -0
  16. package/dist/index.d.cts +859 -0
  17. package/dist/index.d.ts +859 -0
  18. package/dist/index.js +3947 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/theme-init.cjs +12 -0
  21. package/dist/theme-init.cjs.map +1 -0
  22. package/dist/theme-init.d.cts +11 -0
  23. package/dist/theme-init.d.ts +11 -0
  24. package/dist/theme-init.js +8 -0
  25. package/dist/theme-init.js.map +1 -0
  26. package/dist/ui/alert-dialog.d.ts +15 -0
  27. package/dist/ui/alert-dialog.js +137 -0
  28. package/dist/ui/alert-dialog.js.map +1 -0
  29. package/dist/ui/alert.d.ts +11 -0
  30. package/dist/ui/alert.js +57 -0
  31. package/dist/ui/alert.js.map +1 -0
  32. package/dist/ui/avatar.d.ts +13 -0
  33. package/dist/ui/avatar.js +105 -0
  34. package/dist/ui/avatar.js.map +1 -0
  35. package/dist/ui/badge.d.ts +13 -0
  36. package/dist/ui/badge.js +33 -0
  37. package/dist/ui/badge.js.map +1 -0
  38. package/dist/ui/breadcrumb.d.ts +12 -0
  39. package/dist/ui/breadcrumb.js +121 -0
  40. package/dist/ui/breadcrumb.js.map +1 -0
  41. package/dist/ui/bubble.d.ts +19 -0
  42. package/dist/ui/bubble.js +120 -0
  43. package/dist/ui/bubble.js.map +1 -0
  44. package/dist/ui/button.d.ts +12 -0
  45. package/dist/ui/button.js +59 -0
  46. package/dist/ui/button.js.map +1 -0
  47. package/dist/ui/card.d.ts +10 -0
  48. package/dist/ui/card.js +55 -0
  49. package/dist/ui/card.js.map +1 -0
  50. package/dist/ui/collapsible.d.ts +8 -0
  51. package/dist/ui/collapsible.js +17 -0
  52. package/dist/ui/collapsible.js.map +1 -0
  53. package/dist/ui/dialog.d.ts +16 -0
  54. package/dist/ui/dialog.js +125 -0
  55. package/dist/ui/dialog.js.map +1 -0
  56. package/dist/ui/dropdown-menu.d.ts +31 -0
  57. package/dist/ui/dropdown-menu.js +244 -0
  58. package/dist/ui/dropdown-menu.js.map +1 -0
  59. package/dist/ui/input-otp.d.ts +39 -0
  60. package/dist/ui/input-otp.js +53 -0
  61. package/dist/ui/input-otp.js.map +1 -0
  62. package/dist/ui/input.d.ts +5 -0
  63. package/dist/ui/input.js +28 -0
  64. package/dist/ui/input.js.map +1 -0
  65. package/dist/ui/label.d.ts +7 -0
  66. package/dist/ui/label.js +29 -0
  67. package/dist/ui/label.js.map +1 -0
  68. package/dist/ui/message.d.ts +12 -0
  69. package/dist/ui/message.js +93 -0
  70. package/dist/ui/message.js.map +1 -0
  71. package/dist/ui/progress.d.ts +9 -0
  72. package/dist/ui/progress.js +41 -0
  73. package/dist/ui/progress.js.map +1 -0
  74. package/dist/ui/separator.d.ts +6 -0
  75. package/dist/ui/separator.js +31 -0
  76. package/dist/ui/separator.js.map +1 -0
  77. package/dist/ui/sheet.d.ts +16 -0
  78. package/dist/ui/sheet.js +127 -0
  79. package/dist/ui/sheet.js.map +1 -0
  80. package/dist/ui/sidebar.d.ts +69 -0
  81. package/dist/ui/sidebar.js +624 -0
  82. package/dist/ui/sidebar.js.map +1 -0
  83. package/dist/ui/skeleton.d.ts +5 -0
  84. package/dist/ui/skeleton.js +26 -0
  85. package/dist/ui/skeleton.js.map +1 -0
  86. package/dist/ui/sonner.d.ts +6 -0
  87. package/dist/ui/sonner.js +25 -0
  88. package/dist/ui/sonner.js.map +1 -0
  89. package/dist/ui/switch.d.ts +8 -0
  90. package/dist/ui/switch.js +38 -0
  91. package/dist/ui/switch.js.map +1 -0
  92. package/dist/ui/table.d.ts +11 -0
  93. package/dist/ui/table.js +85 -0
  94. package/dist/ui/table.js.map +1 -0
  95. package/dist/ui/tooltip.d.ts +9 -0
  96. package/dist/ui/tooltip.js +67 -0
  97. package/dist/ui/tooltip.js.map +1 -0
  98. package/dist/ui/use-mobile.d.ts +3 -0
  99. package/dist/ui/use-mobile.js +24 -0
  100. package/dist/ui/use-mobile.js.map +1 -0
  101. package/package.json +281 -5
  102. package/scripts/check-theme.mjs +74 -0
  103. package/src/theme.css +191 -0
package/README.md CHANGED
@@ -1,3 +1,101 @@
1
- # Temporary Holding Version
1
+ # @yenio-pld/blocks
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Shared UI for Yenio applications: shadcn primitives, the provisional theme, product chrome, and PLD components.
4
+
5
+ The main entry is a Client Component boundary (`"use client"`). Import avatar helpers from `@yenio-pld/blocks/avatar` and cookie helpers from `@yenio-pld/blocks/consent` in React Server Components. Import `THEME_INIT_SCRIPT` from `@yenio-pld/blocks/theme-init` in root layouts. That entry has no client boundary.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @yenio-pld/blocks
11
+ ```
12
+
13
+ Peers:
14
+
15
+ - `framer-motion` — `ThemeSwitcher`, `LanguageSwitcher`, `AvatarEditorDialog`
16
+ - `next-themes` — `ThemeSwitcher` and `@yenio-pld/blocks/ui/sonner`
17
+ - `lucide-react` — icons
18
+ - `react` / `react-dom`
19
+
20
+ `next-themes` contract: `attribute="class"`, `storageKey="theme"`, default `system`.
21
+
22
+ ## Tailwind
23
+
24
+ Scan the built package so utilities are not purged:
25
+
26
+ ```css
27
+ @source "../../node_modules/@yenio-pld/blocks/dist/**/*.{js,cjs,mjs}";
28
+ ```
29
+
30
+ Import the shared light and dark palette. Do not paste `:root` / `.dark` color tokens into the app. `src/theme.css` is the only color-token source. The palette is provisional (precise, calm, trustworthy) until brand sign-off.
31
+
32
+ ```css
33
+ @import "tailwindcss";
34
+ @import "@yenio-pld/blocks/theme.css";
35
+ ```
36
+
37
+ `theme.css` is CSS only. It does not pull the client barrel. The `@source` glob is recursive so classes in `dist/ui` are kept.
38
+
39
+ Fail the app build if a stylesheet redeclares those tokens:
40
+
41
+ ```bash
42
+ node node_modules/@yenio-pld/blocks/scripts/check-theme.mjs src
43
+ ```
44
+
45
+ Skeleton loading uses `--skeleton` / `bg-skeleton` (chroma 0), not `--primary` or `--accent`.
46
+
47
+ ## UI primitives
48
+
49
+ Import each shadcn primitive from its own entry. A button import does not load the sidebar, the toast library, or the main chrome barrel:
50
+
51
+ ```tsx
52
+ import { Button } from "@yenio-pld/blocks/ui/button";
53
+ import { Dialog, DialogContent } from "@yenio-pld/blocks/ui/dialog";
54
+ ```
55
+
56
+ `@yenio-pld/blocks/ui/sonner` needs the `next-themes` peer. Product chrome (empty state, confirm dialog, bell, switchers, legal, upload) and the PLD components stay on the main entry.
57
+
58
+ ## PLD components
59
+
60
+ `AuditTimeline`, `RiskBadge`, `EntityGraph`, and `MatchReview` use the platform identifiers (`notice`, `beneficialOwner`, `sat69b`, `alert`). Pass user-visible copy through `labels`. Stories use `es-MX` defaults. English strings in the components are fallbacks.
61
+
62
+ `RiskBadge` does not invent a numeric score. When `smeVerified` is false it shows an unverified-rule label.
63
+
64
+ `MatchReview` calls `onResolve(status, reason)` only with a non-empty reason. A claim with `unverified` or a source URL that is not `https` is labeled unverified and is not styled as confirmed.
65
+
66
+ ## Theme before paint
67
+
68
+ ```tsx
69
+ import {
70
+ THEME_INIT_SCRIPT,
71
+ THEME_INIT_SCRIPT_ID,
72
+ } from "@yenio-pld/blocks/theme-init";
73
+
74
+ <script
75
+ id={THEME_INIT_SCRIPT_ID}
76
+ dangerouslySetInnerHTML={{ __html: THEME_INIT_SCRIPT }}
77
+ />;
78
+ ```
79
+
80
+ ## i18n
81
+
82
+ `LanguageSwitcher` defaults to `es-MX` and `en`. Pass `labels` from next-intl in the apps.
83
+
84
+ ## Storybook
85
+
86
+ Stories live under `src/**/*.stories.tsx`. They are not part of the published package.
87
+
88
+ ```bash
89
+ pnpm storybook
90
+ pnpm build-storybook
91
+ ```
92
+
93
+ Storybook serves on port 6006. The static build writes `storybook-static/`. Preview CSS imports `src/theme.css` and loads Inter.
94
+
95
+ ## Quality gate
96
+
97
+ ```bash
98
+ pnpm run ci:check
99
+ ```
100
+
101
+ That runs format, lint, typecheck, Vitest coverage (85% lines, functions, statements, and branches), the theme-token check, and the package build. GitHub CI also builds Storybook. Versions are owned by semantic-release (`dev` publishes the `rc` channel). Do not hand-bump `package.json`.
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ // src/lib/avatar.ts
4
+ var EMPTY_COLORS = {
5
+ backgroundColor: "#9ca3af",
6
+ color: "#111111"
7
+ };
8
+ var AVATAR_PALETTE = [
9
+ "#4f6d8c",
10
+ // slate blue
11
+ "#3d7a6a",
12
+ // teal
13
+ "#6b5b8a",
14
+ // muted purple
15
+ "#8a5a4a",
16
+ // warm brown
17
+ "#4a7a8a",
18
+ // steel cyan
19
+ "#7a6b4a",
20
+ // olive
21
+ "#5a6b8a",
22
+ // periwinkle
23
+ "#8a4a5a",
24
+ // rose
25
+ "#4a8a6b",
26
+ // green
27
+ "#6b4a7a",
28
+ // plum
29
+ "#8a6b4a",
30
+ // amber brown
31
+ "#4a5a8a",
32
+ // indigo
33
+ "#7a4a6b",
34
+ // mauve
35
+ "#5a8a4a",
36
+ // leaf
37
+ "#8a5a7a",
38
+ // dusty pink
39
+ "#3d6b7a"
40
+ // deep teal
41
+ ];
42
+ function hashString(input) {
43
+ let hash = 2166136261;
44
+ for (let i = 0; i < input.length; i++) {
45
+ hash ^= input.charCodeAt(i);
46
+ hash = Math.imul(hash, 16777619);
47
+ }
48
+ return hash >>> 0;
49
+ }
50
+ function srgbToLinear(channel) {
51
+ const c = channel / 255;
52
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
53
+ }
54
+ function relativeLuminance(hex) {
55
+ const normalized = hex.replace("#", "");
56
+ const r = Number.parseInt(normalized.slice(0, 2), 16);
57
+ const g = Number.parseInt(normalized.slice(2, 4), 16);
58
+ const b = Number.parseInt(normalized.slice(4, 6), 16);
59
+ return 0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b);
60
+ }
61
+ function contrastingTextColor(backgroundHex) {
62
+ return relativeLuminance(backgroundHex) > 0.179 ? "#111111" : "#ffffff";
63
+ }
64
+ function getInitials(name, max = 2) {
65
+ const parts = name.trim().split(/\s+/).filter(Boolean);
66
+ if (parts.length === 0) return "?";
67
+ return parts.slice(0, Math.max(1, max)).map((part) => part.charAt(0)).join("").toUpperCase();
68
+ }
69
+ function getAvatarColors(name) {
70
+ const normalized = name.trim().toLowerCase();
71
+ if (!normalized) {
72
+ return { ...EMPTY_COLORS };
73
+ }
74
+ const backgroundColor = AVATAR_PALETTE[hashString(normalized) % AVATAR_PALETTE.length];
75
+ return {
76
+ backgroundColor,
77
+ color: contrastingTextColor(backgroundColor)
78
+ };
79
+ }
80
+ function avatarFallbackStyle(name) {
81
+ return getAvatarColors(name);
82
+ }
83
+
84
+ exports.avatarFallbackStyle = avatarFallbackStyle;
85
+ exports.getAvatarColors = getAvatarColors;
86
+ exports.getInitials = getInitials;
87
+ //# sourceMappingURL=avatar.cjs.map
88
+ //# sourceMappingURL=avatar.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/avatar.ts"],"names":[],"mappings":"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","file":"avatar.cjs","sourcesContent":["/** Neutral fallback when the name is empty. */\nconst EMPTY_COLORS = {\n\tbackgroundColor: \"#9ca3af\",\n\tcolor: \"#111111\",\n} as const;\n\n/**\n * Curated mid-lightness palette — professional, calm hues that stay\n * distinct when hashed by name and contrast well with black or white text.\n */\nconst AVATAR_PALETTE = [\n\t\"#4f6d8c\", // slate blue\n\t\"#3d7a6a\", // teal\n\t\"#6b5b8a\", // muted purple\n\t\"#8a5a4a\", // warm brown\n\t\"#4a7a8a\", // steel cyan\n\t\"#7a6b4a\", // olive\n\t\"#5a6b8a\", // periwinkle\n\t\"#8a4a5a\", // rose\n\t\"#4a8a6b\", // green\n\t\"#6b4a7a\", // plum\n\t\"#8a6b4a\", // amber brown\n\t\"#4a5a8a\", // indigo\n\t\"#7a4a6b\", // mauve\n\t\"#5a8a4a\", // leaf\n\t\"#8a5a7a\", // dusty pink\n\t\"#3d6b7a\", // deep teal\n] as const;\n\nexport type AvatarColors = {\n\tbackgroundColor: string;\n\tcolor: string;\n};\n\n/** FNV-1a 32-bit hash — stable across runtimes for the same string. */\nfunction hashString(input: string): number {\n\tlet hash = 0x811c9dc5;\n\tfor (let i = 0; i < input.length; i++) {\n\t\thash ^= input.charCodeAt(i);\n\t\thash = Math.imul(hash, 0x01000193);\n\t}\n\treturn hash >>> 0;\n}\n\nfunction srgbToLinear(channel: number): number {\n\tconst c = channel / 255;\n\treturn c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n}\n\n/** Relative luminance (WCAG) for a hex color like `#rrggbb`. */\nfunction relativeLuminance(hex: string): number {\n\tconst normalized = hex.replace(\"#\", \"\");\n\tconst r = Number.parseInt(normalized.slice(0, 2), 16);\n\tconst g = Number.parseInt(normalized.slice(2, 4), 16);\n\tconst b = Number.parseInt(normalized.slice(4, 6), 16);\n\treturn (\n\t\t0.2126 * srgbToLinear(r) +\n\t\t0.7152 * srgbToLinear(g) +\n\t\t0.0722 * srgbToLinear(b)\n\t);\n}\n\nfunction contrastingTextColor(backgroundHex: string): \"#ffffff\" | \"#111111\" {\n\t// Threshold ~0.179 ≈ contrast preference for mid tones (WCAG-inspired).\n\treturn relativeLuminance(backgroundHex) > 0.179 ? \"#111111\" : \"#ffffff\";\n}\n\n/**\n * Word-based initials from a display name (max letters, default 2).\n * Empty / whitespace-only names yield `\"?\"`.\n */\nexport function getInitials(name: string, max = 2): string {\n\tconst parts = name.trim().split(/\\s+/).filter(Boolean);\n\tif (parts.length === 0) return \"?\";\n\treturn parts\n\t\t.slice(0, Math.max(1, max))\n\t\t.map((part) => part.charAt(0))\n\t\t.join(\"\")\n\t\t.toUpperCase();\n}\n\n/**\n * Deterministic background + contrasting foreground from a full name.\n * Same name (case-insensitive) always maps to the same colors.\n */\nexport function getAvatarColors(name: string): AvatarColors {\n\tconst normalized = name.trim().toLowerCase();\n\tif (!normalized) {\n\t\treturn { ...EMPTY_COLORS };\n\t}\n\tconst backgroundColor =\n\t\tAVATAR_PALETTE[hashString(normalized) % AVATAR_PALETTE.length]!;\n\treturn {\n\t\tbackgroundColor,\n\t\tcolor: contrastingTextColor(backgroundColor),\n\t};\n}\n\n/** CSS style object for `AvatarFallback` (or any letter-avatar container). */\nexport function avatarFallbackStyle(\n\tname: string,\n): Pick<AvatarColors, \"backgroundColor\" | \"color\"> {\n\treturn getAvatarColors(name);\n}\n"]}
@@ -0,0 +1,18 @@
1
+ type AvatarColors = {
2
+ backgroundColor: string;
3
+ color: string;
4
+ };
5
+ /**
6
+ * Word-based initials from a display name (max letters, default 2).
7
+ * Empty / whitespace-only names yield `"?"`.
8
+ */
9
+ declare function getInitials(name: string, max?: number): string;
10
+ /**
11
+ * Deterministic background + contrasting foreground from a full name.
12
+ * Same name (case-insensitive) always maps to the same colors.
13
+ */
14
+ declare function getAvatarColors(name: string): AvatarColors;
15
+ /** CSS style object for `AvatarFallback` (or any letter-avatar container). */
16
+ declare function avatarFallbackStyle(name: string): Pick<AvatarColors, "backgroundColor" | "color">;
17
+
18
+ export { type AvatarColors, avatarFallbackStyle, getAvatarColors, getInitials };
@@ -0,0 +1,18 @@
1
+ type AvatarColors = {
2
+ backgroundColor: string;
3
+ color: string;
4
+ };
5
+ /**
6
+ * Word-based initials from a display name (max letters, default 2).
7
+ * Empty / whitespace-only names yield `"?"`.
8
+ */
9
+ declare function getInitials(name: string, max?: number): string;
10
+ /**
11
+ * Deterministic background + contrasting foreground from a full name.
12
+ * Same name (case-insensitive) always maps to the same colors.
13
+ */
14
+ declare function getAvatarColors(name: string): AvatarColors;
15
+ /** CSS style object for `AvatarFallback` (or any letter-avatar container). */
16
+ declare function avatarFallbackStyle(name: string): Pick<AvatarColors, "backgroundColor" | "color">;
17
+
18
+ export { type AvatarColors, avatarFallbackStyle, getAvatarColors, getInitials };
package/dist/avatar.js ADDED
@@ -0,0 +1,84 @@
1
+ // src/lib/avatar.ts
2
+ var EMPTY_COLORS = {
3
+ backgroundColor: "#9ca3af",
4
+ color: "#111111"
5
+ };
6
+ var AVATAR_PALETTE = [
7
+ "#4f6d8c",
8
+ // slate blue
9
+ "#3d7a6a",
10
+ // teal
11
+ "#6b5b8a",
12
+ // muted purple
13
+ "#8a5a4a",
14
+ // warm brown
15
+ "#4a7a8a",
16
+ // steel cyan
17
+ "#7a6b4a",
18
+ // olive
19
+ "#5a6b8a",
20
+ // periwinkle
21
+ "#8a4a5a",
22
+ // rose
23
+ "#4a8a6b",
24
+ // green
25
+ "#6b4a7a",
26
+ // plum
27
+ "#8a6b4a",
28
+ // amber brown
29
+ "#4a5a8a",
30
+ // indigo
31
+ "#7a4a6b",
32
+ // mauve
33
+ "#5a8a4a",
34
+ // leaf
35
+ "#8a5a7a",
36
+ // dusty pink
37
+ "#3d6b7a"
38
+ // deep teal
39
+ ];
40
+ function hashString(input) {
41
+ let hash = 2166136261;
42
+ for (let i = 0; i < input.length; i++) {
43
+ hash ^= input.charCodeAt(i);
44
+ hash = Math.imul(hash, 16777619);
45
+ }
46
+ return hash >>> 0;
47
+ }
48
+ function srgbToLinear(channel) {
49
+ const c = channel / 255;
50
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
51
+ }
52
+ function relativeLuminance(hex) {
53
+ const normalized = hex.replace("#", "");
54
+ const r = Number.parseInt(normalized.slice(0, 2), 16);
55
+ const g = Number.parseInt(normalized.slice(2, 4), 16);
56
+ const b = Number.parseInt(normalized.slice(4, 6), 16);
57
+ return 0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b);
58
+ }
59
+ function contrastingTextColor(backgroundHex) {
60
+ return relativeLuminance(backgroundHex) > 0.179 ? "#111111" : "#ffffff";
61
+ }
62
+ function getInitials(name, max = 2) {
63
+ const parts = name.trim().split(/\s+/).filter(Boolean);
64
+ if (parts.length === 0) return "?";
65
+ return parts.slice(0, Math.max(1, max)).map((part) => part.charAt(0)).join("").toUpperCase();
66
+ }
67
+ function getAvatarColors(name) {
68
+ const normalized = name.trim().toLowerCase();
69
+ if (!normalized) {
70
+ return { ...EMPTY_COLORS };
71
+ }
72
+ const backgroundColor = AVATAR_PALETTE[hashString(normalized) % AVATAR_PALETTE.length];
73
+ return {
74
+ backgroundColor,
75
+ color: contrastingTextColor(backgroundColor)
76
+ };
77
+ }
78
+ function avatarFallbackStyle(name) {
79
+ return getAvatarColors(name);
80
+ }
81
+
82
+ export { avatarFallbackStyle, getAvatarColors, getInitials };
83
+ //# sourceMappingURL=avatar.js.map
84
+ //# sourceMappingURL=avatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/avatar.ts"],"names":[],"mappings":"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","file":"avatar.js","sourcesContent":["/** Neutral fallback when the name is empty. */\nconst EMPTY_COLORS = {\n\tbackgroundColor: \"#9ca3af\",\n\tcolor: \"#111111\",\n} as const;\n\n/**\n * Curated mid-lightness palette — professional, calm hues that stay\n * distinct when hashed by name and contrast well with black or white text.\n */\nconst AVATAR_PALETTE = [\n\t\"#4f6d8c\", // slate blue\n\t\"#3d7a6a\", // teal\n\t\"#6b5b8a\", // muted purple\n\t\"#8a5a4a\", // warm brown\n\t\"#4a7a8a\", // steel cyan\n\t\"#7a6b4a\", // olive\n\t\"#5a6b8a\", // periwinkle\n\t\"#8a4a5a\", // rose\n\t\"#4a8a6b\", // green\n\t\"#6b4a7a\", // plum\n\t\"#8a6b4a\", // amber brown\n\t\"#4a5a8a\", // indigo\n\t\"#7a4a6b\", // mauve\n\t\"#5a8a4a\", // leaf\n\t\"#8a5a7a\", // dusty pink\n\t\"#3d6b7a\", // deep teal\n] as const;\n\nexport type AvatarColors = {\n\tbackgroundColor: string;\n\tcolor: string;\n};\n\n/** FNV-1a 32-bit hash — stable across runtimes for the same string. */\nfunction hashString(input: string): number {\n\tlet hash = 0x811c9dc5;\n\tfor (let i = 0; i < input.length; i++) {\n\t\thash ^= input.charCodeAt(i);\n\t\thash = Math.imul(hash, 0x01000193);\n\t}\n\treturn hash >>> 0;\n}\n\nfunction srgbToLinear(channel: number): number {\n\tconst c = channel / 255;\n\treturn c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n}\n\n/** Relative luminance (WCAG) for a hex color like `#rrggbb`. */\nfunction relativeLuminance(hex: string): number {\n\tconst normalized = hex.replace(\"#\", \"\");\n\tconst r = Number.parseInt(normalized.slice(0, 2), 16);\n\tconst g = Number.parseInt(normalized.slice(2, 4), 16);\n\tconst b = Number.parseInt(normalized.slice(4, 6), 16);\n\treturn (\n\t\t0.2126 * srgbToLinear(r) +\n\t\t0.7152 * srgbToLinear(g) +\n\t\t0.0722 * srgbToLinear(b)\n\t);\n}\n\nfunction contrastingTextColor(backgroundHex: string): \"#ffffff\" | \"#111111\" {\n\t// Threshold ~0.179 ≈ contrast preference for mid tones (WCAG-inspired).\n\treturn relativeLuminance(backgroundHex) > 0.179 ? \"#111111\" : \"#ffffff\";\n}\n\n/**\n * Word-based initials from a display name (max letters, default 2).\n * Empty / whitespace-only names yield `\"?\"`.\n */\nexport function getInitials(name: string, max = 2): string {\n\tconst parts = name.trim().split(/\\s+/).filter(Boolean);\n\tif (parts.length === 0) return \"?\";\n\treturn parts\n\t\t.slice(0, Math.max(1, max))\n\t\t.map((part) => part.charAt(0))\n\t\t.join(\"\")\n\t\t.toUpperCase();\n}\n\n/**\n * Deterministic background + contrasting foreground from a full name.\n * Same name (case-insensitive) always maps to the same colors.\n */\nexport function getAvatarColors(name: string): AvatarColors {\n\tconst normalized = name.trim().toLowerCase();\n\tif (!normalized) {\n\t\treturn { ...EMPTY_COLORS };\n\t}\n\tconst backgroundColor =\n\t\tAVATAR_PALETTE[hashString(normalized) % AVATAR_PALETTE.length]!;\n\treturn {\n\t\tbackgroundColor,\n\t\tcolor: contrastingTextColor(backgroundColor),\n\t};\n}\n\n/** CSS style object for `AvatarFallback` (or any letter-avatar container). */\nexport function avatarFallbackStyle(\n\tname: string,\n): Pick<AvatarColors, \"backgroundColor\" | \"color\"> {\n\treturn getAvatarColors(name);\n}\n"]}
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ // src/lib/cookie-consent.ts
4
+ var COOKIE_CONSENT_NAME = "yenio-cookie-consent";
5
+ var COOKIE_CONSENT_VALUES = ["necessary", "all"];
6
+ var COOKIE_CONSENT_MAX_AGE = 180 * 24 * 60 * 60;
7
+ function isCookieConsentValue(value) {
8
+ return typeof value === "string" && COOKIE_CONSENT_VALUES.includes(value);
9
+ }
10
+ function cookieConsentAllowsReplay(value) {
11
+ return value === "all";
12
+ }
13
+ function cookieDomainFromHostname(hostname) {
14
+ const host = hostname.toLowerCase();
15
+ if (host === "localhost" || host === "127.0.0.1") return void 0;
16
+ if (host.endsWith(".yenio.mx") || host === "yenio.mx") return ".yenio.mx";
17
+ if (host.endsWith(".yenio-pld.workers.dev") || host === "yenio-pld.workers.dev") {
18
+ return ".yenio-pld.workers.dev";
19
+ }
20
+ return void 0;
21
+ }
22
+ function readCookieMap(cookieSource) {
23
+ const map = /* @__PURE__ */ new Map();
24
+ for (const part of cookieSource.split(";")) {
25
+ const trimmed = part.trim();
26
+ if (!trimmed) continue;
27
+ const eq = trimmed.indexOf("=");
28
+ if (eq < 0) continue;
29
+ const name = trimmed.slice(0, eq).trim();
30
+ const raw = trimmed.slice(eq + 1).trim();
31
+ map.set(name, decodeURIComponent(raw));
32
+ }
33
+ return map;
34
+ }
35
+ function parseCookieConsent(cookieSource) {
36
+ const raw = readCookieMap(cookieSource).get(COOKIE_CONSENT_NAME);
37
+ return isCookieConsentValue(raw) ? raw : null;
38
+ }
39
+ function readCookieConsent() {
40
+ if (typeof document === "undefined") return null;
41
+ return parseCookieConsent(document.cookie);
42
+ }
43
+ function serializeCookieConsent(value, options = {}) {
44
+ const maxAge = options.maxAge ?? COOKIE_CONSENT_MAX_AGE;
45
+ const secure = options.secure ?? (typeof window !== "undefined" && window.location.protocol === "https:");
46
+ const parts = [
47
+ `${COOKIE_CONSENT_NAME}=${encodeURIComponent(value)}`,
48
+ "Path=/",
49
+ `Max-Age=${maxAge}`,
50
+ "SameSite=Lax"
51
+ ];
52
+ if (options.domain) {
53
+ parts.push(`Domain=${options.domain}`);
54
+ }
55
+ if (secure) {
56
+ parts.push("Secure");
57
+ }
58
+ return parts.join("; ");
59
+ }
60
+ function writeCookieConsent(value, options = {}) {
61
+ if (typeof document === "undefined") return;
62
+ document.cookie = serializeCookieConsent(value, options);
63
+ }
64
+ function isEmbeddedWindow() {
65
+ if (typeof window === "undefined") return false;
66
+ try {
67
+ return window.self !== window.top;
68
+ } catch {
69
+ return true;
70
+ }
71
+ }
72
+
73
+ exports.COOKIE_CONSENT_MAX_AGE = COOKIE_CONSENT_MAX_AGE;
74
+ exports.COOKIE_CONSENT_NAME = COOKIE_CONSENT_NAME;
75
+ exports.COOKIE_CONSENT_VALUES = COOKIE_CONSENT_VALUES;
76
+ exports.cookieConsentAllowsReplay = cookieConsentAllowsReplay;
77
+ exports.cookieDomainFromHostname = cookieDomainFromHostname;
78
+ exports.isCookieConsentValue = isCookieConsentValue;
79
+ exports.isEmbeddedWindow = isEmbeddedWindow;
80
+ exports.parseCookieConsent = parseCookieConsent;
81
+ exports.readCookieConsent = readCookieConsent;
82
+ exports.serializeCookieConsent = serializeCookieConsent;
83
+ exports.writeCookieConsent = writeCookieConsent;
84
+ //# sourceMappingURL=consent.cjs.map
85
+ //# sourceMappingURL=consent.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/cookie-consent.ts"],"names":[],"mappings":"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","file":"consent.cjs","sourcesContent":["export const COOKIE_CONSENT_NAME = \"yenio-cookie-consent\";\n\nexport const COOKIE_CONSENT_VALUES = [\"necessary\", \"all\"] as const;\n\nexport type CookieConsentValue = (typeof COOKIE_CONSENT_VALUES)[number];\n\n/** ~180 days, matching contracts §1.2. */\nexport const COOKIE_CONSENT_MAX_AGE = 180 * 24 * 60 * 60;\n\nexport type WriteCookieConsentOptions = {\n\tdomain?: string;\n\tsecure?: boolean;\n\tmaxAge?: number;\n};\n\nexport function isCookieConsentValue(\n\tvalue: unknown,\n): value is CookieConsentValue {\n\treturn (\n\t\ttypeof value === \"string\" &&\n\t\t(COOKIE_CONSENT_VALUES as readonly string[]).includes(value)\n\t);\n}\n\nexport function cookieConsentAllowsReplay(\n\tvalue: CookieConsentValue | null | undefined,\n): boolean {\n\treturn value === \"all\";\n}\n\nexport function cookieDomainFromHostname(hostname: string): string | undefined {\n\tconst host = hostname.toLowerCase();\n\tif (host === \"localhost\" || host === \"127.0.0.1\") return undefined;\n\tif (host.endsWith(\".yenio.mx\") || host === \"yenio.mx\") return \".yenio.mx\";\n\tif (\n\t\thost.endsWith(\".yenio-pld.workers.dev\") ||\n\t\thost === \"yenio-pld.workers.dev\"\n\t) {\n\t\treturn \".yenio-pld.workers.dev\";\n\t}\n\treturn undefined;\n}\n\nfunction readCookieMap(cookieSource: string): Map<string, string> {\n\tconst map = new Map<string, string>();\n\tfor (const part of cookieSource.split(\";\")) {\n\t\tconst trimmed = part.trim();\n\t\tif (!trimmed) continue;\n\t\tconst eq = trimmed.indexOf(\"=\");\n\t\tif (eq < 0) continue;\n\t\tconst name = trimmed.slice(0, eq).trim();\n\t\tconst raw = trimmed.slice(eq + 1).trim();\n\t\tmap.set(name, decodeURIComponent(raw));\n\t}\n\treturn map;\n}\n\nexport function parseCookieConsent(\n\tcookieSource: string,\n): CookieConsentValue | null {\n\tconst raw = readCookieMap(cookieSource).get(COOKIE_CONSENT_NAME);\n\treturn isCookieConsentValue(raw) ? raw : null;\n}\n\nexport function readCookieConsent(): CookieConsentValue | null {\n\tif (typeof document === \"undefined\") return null;\n\treturn parseCookieConsent(document.cookie);\n}\n\nexport function serializeCookieConsent(\n\tvalue: CookieConsentValue,\n\toptions: WriteCookieConsentOptions = {},\n): string {\n\tconst maxAge = options.maxAge ?? COOKIE_CONSENT_MAX_AGE;\n\tconst secure =\n\t\toptions.secure ??\n\t\t(typeof window !== \"undefined\" && window.location.protocol === \"https:\");\n\tconst parts = [\n\t\t`${COOKIE_CONSENT_NAME}=${encodeURIComponent(value)}`,\n\t\t\"Path=/\",\n\t\t`Max-Age=${maxAge}`,\n\t\t\"SameSite=Lax\",\n\t];\n\tif (options.domain) {\n\t\tparts.push(`Domain=${options.domain}`);\n\t}\n\tif (secure) {\n\t\tparts.push(\"Secure\");\n\t}\n\treturn parts.join(\"; \");\n}\n\nexport function writeCookieConsent(\n\tvalue: CookieConsentValue,\n\toptions: WriteCookieConsentOptions = {},\n): void {\n\tif (typeof document === \"undefined\") return;\n\tdocument.cookie = serializeCookieConsent(value, options);\n}\n\nexport function isEmbeddedWindow(): boolean {\n\tif (typeof window === \"undefined\") return false;\n\ttry {\n\t\treturn window.self !== window.top;\n\t} catch {\n\t\treturn true;\n\t}\n}\n"]}
@@ -0,0 +1,20 @@
1
+ declare const COOKIE_CONSENT_NAME = "yenio-cookie-consent";
2
+ declare const COOKIE_CONSENT_VALUES: readonly ["necessary", "all"];
3
+ type CookieConsentValue = (typeof COOKIE_CONSENT_VALUES)[number];
4
+ /** ~180 days, matching contracts §1.2. */
5
+ declare const COOKIE_CONSENT_MAX_AGE: number;
6
+ type WriteCookieConsentOptions = {
7
+ domain?: string;
8
+ secure?: boolean;
9
+ maxAge?: number;
10
+ };
11
+ declare function isCookieConsentValue(value: unknown): value is CookieConsentValue;
12
+ declare function cookieConsentAllowsReplay(value: CookieConsentValue | null | undefined): boolean;
13
+ declare function cookieDomainFromHostname(hostname: string): string | undefined;
14
+ declare function parseCookieConsent(cookieSource: string): CookieConsentValue | null;
15
+ declare function readCookieConsent(): CookieConsentValue | null;
16
+ declare function serializeCookieConsent(value: CookieConsentValue, options?: WriteCookieConsentOptions): string;
17
+ declare function writeCookieConsent(value: CookieConsentValue, options?: WriteCookieConsentOptions): void;
18
+ declare function isEmbeddedWindow(): boolean;
19
+
20
+ export { COOKIE_CONSENT_MAX_AGE, COOKIE_CONSENT_NAME, COOKIE_CONSENT_VALUES, type CookieConsentValue, type WriteCookieConsentOptions, cookieConsentAllowsReplay, cookieDomainFromHostname, isCookieConsentValue, isEmbeddedWindow, parseCookieConsent, readCookieConsent, serializeCookieConsent, writeCookieConsent };
@@ -0,0 +1,20 @@
1
+ declare const COOKIE_CONSENT_NAME = "yenio-cookie-consent";
2
+ declare const COOKIE_CONSENT_VALUES: readonly ["necessary", "all"];
3
+ type CookieConsentValue = (typeof COOKIE_CONSENT_VALUES)[number];
4
+ /** ~180 days, matching contracts §1.2. */
5
+ declare const COOKIE_CONSENT_MAX_AGE: number;
6
+ type WriteCookieConsentOptions = {
7
+ domain?: string;
8
+ secure?: boolean;
9
+ maxAge?: number;
10
+ };
11
+ declare function isCookieConsentValue(value: unknown): value is CookieConsentValue;
12
+ declare function cookieConsentAllowsReplay(value: CookieConsentValue | null | undefined): boolean;
13
+ declare function cookieDomainFromHostname(hostname: string): string | undefined;
14
+ declare function parseCookieConsent(cookieSource: string): CookieConsentValue | null;
15
+ declare function readCookieConsent(): CookieConsentValue | null;
16
+ declare function serializeCookieConsent(value: CookieConsentValue, options?: WriteCookieConsentOptions): string;
17
+ declare function writeCookieConsent(value: CookieConsentValue, options?: WriteCookieConsentOptions): void;
18
+ declare function isEmbeddedWindow(): boolean;
19
+
20
+ export { COOKIE_CONSENT_MAX_AGE, COOKIE_CONSENT_NAME, COOKIE_CONSENT_VALUES, type CookieConsentValue, type WriteCookieConsentOptions, cookieConsentAllowsReplay, cookieDomainFromHostname, isCookieConsentValue, isEmbeddedWindow, parseCookieConsent, readCookieConsent, serializeCookieConsent, writeCookieConsent };
@@ -0,0 +1,73 @@
1
+ // src/lib/cookie-consent.ts
2
+ var COOKIE_CONSENT_NAME = "yenio-cookie-consent";
3
+ var COOKIE_CONSENT_VALUES = ["necessary", "all"];
4
+ var COOKIE_CONSENT_MAX_AGE = 180 * 24 * 60 * 60;
5
+ function isCookieConsentValue(value) {
6
+ return typeof value === "string" && COOKIE_CONSENT_VALUES.includes(value);
7
+ }
8
+ function cookieConsentAllowsReplay(value) {
9
+ return value === "all";
10
+ }
11
+ function cookieDomainFromHostname(hostname) {
12
+ const host = hostname.toLowerCase();
13
+ if (host === "localhost" || host === "127.0.0.1") return void 0;
14
+ if (host.endsWith(".yenio.mx") || host === "yenio.mx") return ".yenio.mx";
15
+ if (host.endsWith(".yenio-pld.workers.dev") || host === "yenio-pld.workers.dev") {
16
+ return ".yenio-pld.workers.dev";
17
+ }
18
+ return void 0;
19
+ }
20
+ function readCookieMap(cookieSource) {
21
+ const map = /* @__PURE__ */ new Map();
22
+ for (const part of cookieSource.split(";")) {
23
+ const trimmed = part.trim();
24
+ if (!trimmed) continue;
25
+ const eq = trimmed.indexOf("=");
26
+ if (eq < 0) continue;
27
+ const name = trimmed.slice(0, eq).trim();
28
+ const raw = trimmed.slice(eq + 1).trim();
29
+ map.set(name, decodeURIComponent(raw));
30
+ }
31
+ return map;
32
+ }
33
+ function parseCookieConsent(cookieSource) {
34
+ const raw = readCookieMap(cookieSource).get(COOKIE_CONSENT_NAME);
35
+ return isCookieConsentValue(raw) ? raw : null;
36
+ }
37
+ function readCookieConsent() {
38
+ if (typeof document === "undefined") return null;
39
+ return parseCookieConsent(document.cookie);
40
+ }
41
+ function serializeCookieConsent(value, options = {}) {
42
+ const maxAge = options.maxAge ?? COOKIE_CONSENT_MAX_AGE;
43
+ const secure = options.secure ?? (typeof window !== "undefined" && window.location.protocol === "https:");
44
+ const parts = [
45
+ `${COOKIE_CONSENT_NAME}=${encodeURIComponent(value)}`,
46
+ "Path=/",
47
+ `Max-Age=${maxAge}`,
48
+ "SameSite=Lax"
49
+ ];
50
+ if (options.domain) {
51
+ parts.push(`Domain=${options.domain}`);
52
+ }
53
+ if (secure) {
54
+ parts.push("Secure");
55
+ }
56
+ return parts.join("; ");
57
+ }
58
+ function writeCookieConsent(value, options = {}) {
59
+ if (typeof document === "undefined") return;
60
+ document.cookie = serializeCookieConsent(value, options);
61
+ }
62
+ function isEmbeddedWindow() {
63
+ if (typeof window === "undefined") return false;
64
+ try {
65
+ return window.self !== window.top;
66
+ } catch {
67
+ return true;
68
+ }
69
+ }
70
+
71
+ export { COOKIE_CONSENT_MAX_AGE, COOKIE_CONSENT_NAME, COOKIE_CONSENT_VALUES, cookieConsentAllowsReplay, cookieDomainFromHostname, isCookieConsentValue, isEmbeddedWindow, parseCookieConsent, readCookieConsent, serializeCookieConsent, writeCookieConsent };
72
+ //# sourceMappingURL=consent.js.map
73
+ //# sourceMappingURL=consent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/cookie-consent.ts"],"names":[],"mappings":"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","file":"consent.js","sourcesContent":["export const COOKIE_CONSENT_NAME = \"yenio-cookie-consent\";\n\nexport const COOKIE_CONSENT_VALUES = [\"necessary\", \"all\"] as const;\n\nexport type CookieConsentValue = (typeof COOKIE_CONSENT_VALUES)[number];\n\n/** ~180 days, matching contracts §1.2. */\nexport const COOKIE_CONSENT_MAX_AGE = 180 * 24 * 60 * 60;\n\nexport type WriteCookieConsentOptions = {\n\tdomain?: string;\n\tsecure?: boolean;\n\tmaxAge?: number;\n};\n\nexport function isCookieConsentValue(\n\tvalue: unknown,\n): value is CookieConsentValue {\n\treturn (\n\t\ttypeof value === \"string\" &&\n\t\t(COOKIE_CONSENT_VALUES as readonly string[]).includes(value)\n\t);\n}\n\nexport function cookieConsentAllowsReplay(\n\tvalue: CookieConsentValue | null | undefined,\n): boolean {\n\treturn value === \"all\";\n}\n\nexport function cookieDomainFromHostname(hostname: string): string | undefined {\n\tconst host = hostname.toLowerCase();\n\tif (host === \"localhost\" || host === \"127.0.0.1\") return undefined;\n\tif (host.endsWith(\".yenio.mx\") || host === \"yenio.mx\") return \".yenio.mx\";\n\tif (\n\t\thost.endsWith(\".yenio-pld.workers.dev\") ||\n\t\thost === \"yenio-pld.workers.dev\"\n\t) {\n\t\treturn \".yenio-pld.workers.dev\";\n\t}\n\treturn undefined;\n}\n\nfunction readCookieMap(cookieSource: string): Map<string, string> {\n\tconst map = new Map<string, string>();\n\tfor (const part of cookieSource.split(\";\")) {\n\t\tconst trimmed = part.trim();\n\t\tif (!trimmed) continue;\n\t\tconst eq = trimmed.indexOf(\"=\");\n\t\tif (eq < 0) continue;\n\t\tconst name = trimmed.slice(0, eq).trim();\n\t\tconst raw = trimmed.slice(eq + 1).trim();\n\t\tmap.set(name, decodeURIComponent(raw));\n\t}\n\treturn map;\n}\n\nexport function parseCookieConsent(\n\tcookieSource: string,\n): CookieConsentValue | null {\n\tconst raw = readCookieMap(cookieSource).get(COOKIE_CONSENT_NAME);\n\treturn isCookieConsentValue(raw) ? raw : null;\n}\n\nexport function readCookieConsent(): CookieConsentValue | null {\n\tif (typeof document === \"undefined\") return null;\n\treturn parseCookieConsent(document.cookie);\n}\n\nexport function serializeCookieConsent(\n\tvalue: CookieConsentValue,\n\toptions: WriteCookieConsentOptions = {},\n): string {\n\tconst maxAge = options.maxAge ?? COOKIE_CONSENT_MAX_AGE;\n\tconst secure =\n\t\toptions.secure ??\n\t\t(typeof window !== \"undefined\" && window.location.protocol === \"https:\");\n\tconst parts = [\n\t\t`${COOKIE_CONSENT_NAME}=${encodeURIComponent(value)}`,\n\t\t\"Path=/\",\n\t\t`Max-Age=${maxAge}`,\n\t\t\"SameSite=Lax\",\n\t];\n\tif (options.domain) {\n\t\tparts.push(`Domain=${options.domain}`);\n\t}\n\tif (secure) {\n\t\tparts.push(\"Secure\");\n\t}\n\treturn parts.join(\"; \");\n}\n\nexport function writeCookieConsent(\n\tvalue: CookieConsentValue,\n\toptions: WriteCookieConsentOptions = {},\n): void {\n\tif (typeof document === \"undefined\") return;\n\tdocument.cookie = serializeCookieConsent(value, options);\n}\n\nexport function isEmbeddedWindow(): boolean {\n\tif (typeof window === \"undefined\") return false;\n\ttry {\n\t\treturn window.self !== window.top;\n\t} catch {\n\t\treturn true;\n\t}\n}\n"]}