@atmo-dev/events-ui 0.1.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 (113) hide show
  1. package/dist/DatePicker.svelte +231 -0
  2. package/dist/DatePicker.svelte.d.ts +11 -0
  3. package/dist/DateTimePicker.svelte +101 -0
  4. package/dist/DateTimePicker.svelte.d.ts +9 -0
  5. package/dist/EventAttendees.svelte +203 -0
  6. package/dist/EventAttendees.svelte.d.ts +13 -0
  7. package/dist/EventCard.svelte +131 -0
  8. package/dist/EventCard.svelte.d.ts +8 -0
  9. package/dist/EventComments.svelte +99 -0
  10. package/dist/EventComments.svelte.d.ts +6 -0
  11. package/dist/EventEditor.svelte +589 -0
  12. package/dist/EventEditor.svelte.d.ts +20 -0
  13. package/dist/EventRsvp.svelte +237 -0
  14. package/dist/EventRsvp.svelte.d.ts +17 -0
  15. package/dist/EventView.svelte +433 -0
  16. package/dist/EventView.svelte.d.ts +16 -0
  17. package/dist/ImageDropper.svelte +66 -0
  18. package/dist/ImageDropper.svelte.d.ts +7 -0
  19. package/dist/Map.svelte +27 -0
  20. package/dist/Map.svelte.d.ts +8 -0
  21. package/dist/PostToBlueskyModal.svelte +244 -0
  22. package/dist/PostToBlueskyModal.svelte.d.ts +22 -0
  23. package/dist/ShareModal.svelte +160 -0
  24. package/dist/ShareModal.svelte.d.ts +23 -0
  25. package/dist/ThemeApply.svelte +50 -0
  26. package/dist/ThemeApply.svelte.d.ts +7 -0
  27. package/dist/ThemeBackground.svelte +33 -0
  28. package/dist/ThemeBackground.svelte.d.ts +7 -0
  29. package/dist/ThemePicker.svelte +102 -0
  30. package/dist/ThemePicker.svelte.d.ts +7 -0
  31. package/dist/ThumbnailPresets.svelte +68 -0
  32. package/dist/ThumbnailPresets.svelte.d.ts +11 -0
  33. package/dist/TimePicker.svelte +188 -0
  34. package/dist/TimePicker.svelte.d.ts +9 -0
  35. package/dist/TimezonePicker.svelte +132 -0
  36. package/dist/TimezonePicker.svelte.d.ts +6 -0
  37. package/dist/VodPlayer.svelte +137 -0
  38. package/dist/VodPlayer.svelte.d.ts +14 -0
  39. package/dist/VodTranscript.svelte +72 -0
  40. package/dist/VodTranscript.svelte.d.ts +8 -0
  41. package/dist/atproto-helpers.d.ts +21 -0
  42. package/dist/atproto-helpers.js +61 -0
  43. package/dist/cal/helper.d.ts +1 -0
  44. package/dist/cal/helper.js +20 -0
  45. package/dist/cal/ical.d.ts +22 -0
  46. package/dist/cal/ical.js +188 -0
  47. package/dist/cal/sanitize.d.ts +3 -0
  48. package/dist/cal/sanitize.js +25 -0
  49. package/dist/contrail.d.ts +54 -0
  50. package/dist/contrail.js +22 -0
  51. package/dist/date-format.d.ts +22 -0
  52. package/dist/date-format.js +43 -0
  53. package/dist/editor/LinksSection.svelte +144 -0
  54. package/dist/editor/LinksSection.svelte.d.ts +10 -0
  55. package/dist/editor/LocationSection.svelte +215 -0
  56. package/dist/editor/LocationSection.svelte.d.ts +8 -0
  57. package/dist/editor/RecurringModal.svelte +270 -0
  58. package/dist/editor/RecurringModal.svelte.d.ts +30 -0
  59. package/dist/editor/ThemeSection.svelte +39 -0
  60. package/dist/editor/ThemeSection.svelte.d.ts +7 -0
  61. package/dist/editor/ThumbnailSection.svelte +219 -0
  62. package/dist/editor/ThumbnailSection.svelte.d.ts +13 -0
  63. package/dist/editor/adapter.d.ts +98 -0
  64. package/dist/editor/adapter.js +9 -0
  65. package/dist/editor/save.d.ts +42 -0
  66. package/dist/editor/save.js +154 -0
  67. package/dist/editor/types.d.ts +39 -0
  68. package/dist/editor/types.js +9 -0
  69. package/dist/event-types.d.ts +70 -0
  70. package/dist/event-types.js +11 -0
  71. package/dist/event-view/AddToCalendarButton.svelte +42 -0
  72. package/dist/event-view/AddToCalendarButton.svelte.d.ts +9 -0
  73. package/dist/event-view/EventBadges.svelte +20 -0
  74. package/dist/event-view/EventBadges.svelte.d.ts +7 -0
  75. package/dist/event-view/EventDateBlock.svelte +43 -0
  76. package/dist/event-view/EventDateBlock.svelte.d.ts +7 -0
  77. package/dist/event-view/EventHostedBy.svelte +63 -0
  78. package/dist/event-view/EventHostedBy.svelte.d.ts +16 -0
  79. package/dist/event-view/EventLinksList.svelte +37 -0
  80. package/dist/event-view/EventLinksList.svelte.d.ts +9 -0
  81. package/dist/event-view/EventLocationBlock.svelte +48 -0
  82. package/dist/event-view/EventLocationBlock.svelte.d.ts +7 -0
  83. package/dist/event-view/EventLocationMap.svelte +72 -0
  84. package/dist/event-view/EventLocationMap.svelte.d.ts +8 -0
  85. package/dist/event-view/ExternalRsvpNotice.svelte +44 -0
  86. package/dist/event-view/ExternalRsvpNotice.svelte.d.ts +6 -0
  87. package/dist/event-view/InviteShareFlow.svelte +177 -0
  88. package/dist/event-view/InviteShareFlow.svelte.d.ts +15 -0
  89. package/dist/event-view/StreamPlacePlayer.svelte +222 -0
  90. package/dist/event-view/StreamPlacePlayer.svelte.d.ts +8 -0
  91. package/dist/event-view/format.d.ts +26 -0
  92. package/dist/event-view/format.js +145 -0
  93. package/dist/index.d.ts +18 -0
  94. package/dist/index.js +18 -0
  95. package/dist/profile-url.d.ts +1 -0
  96. package/dist/profile-url.js +7 -0
  97. package/dist/theme.d.ts +9 -0
  98. package/dist/theme.js +22 -0
  99. package/dist/themes/Blobs.svelte +35 -0
  100. package/dist/themes/Blobs.svelte.d.ts +26 -0
  101. package/dist/themes/Butterflies.svelte +185 -0
  102. package/dist/themes/Butterflies.svelte.d.ts +3 -0
  103. package/dist/themes/Fireflies.svelte +134 -0
  104. package/dist/themes/Fireflies.svelte.d.ts +3 -0
  105. package/dist/themes/Kaleidoscope.svelte +177 -0
  106. package/dist/themes/Kaleidoscope.svelte.d.ts +3 -0
  107. package/dist/themes/Matrix.svelte +150 -0
  108. package/dist/themes/Matrix.svelte.d.ts +3 -0
  109. package/dist/themes/Stars.svelte +98 -0
  110. package/dist/themes/Stars.svelte.d.ts +3 -0
  111. package/dist/thumbnails/designs.d.ts +18 -0
  112. package/dist/thumbnails/designs.js +316 -0
  113. package/package.json +95 -0
@@ -0,0 +1,150 @@
1
+ <script lang="ts">
2
+ import { BROWSER as browser } from 'esm-env';
3
+
4
+ let canvas: HTMLCanvasElement | undefined = $state(undefined);
5
+
6
+ $effect(() => {
7
+ if (!canvas || !browser) return;
8
+
9
+ const ctx = canvas.getContext('2d')!;
10
+ let animId: number;
11
+
12
+ const fontSize = 14;
13
+ const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF';
14
+
15
+ // Speeds in units per second
16
+ const DROP_SPEED = 8; // rows per second
17
+ const FADE_RATE = 2; // alpha per second for trail fade
18
+ const MUTATE_CHANCE = 0.8; // chance per column per second
19
+
20
+ let lastWidth = 0;
21
+ function resize() {
22
+ const w = window.innerWidth;
23
+ if (w === lastWidth) return;
24
+ lastWidth = w;
25
+ canvas!.width = w;
26
+ canvas!.height = window.screen.height;
27
+ }
28
+ resize();
29
+ window.addEventListener('resize', resize);
30
+
31
+ let columns = Math.floor(canvas.width / fontSize);
32
+ let rows = Math.ceil(canvas.height / fontSize) + 1;
33
+ let drops = new Array(columns).fill(0).map(() => Math.random() * -100);
34
+ let grid: string[][] = Array.from({ length: columns }, () =>
35
+ Array.from({ length: rows }, () => chars[Math.floor(Math.random() * chars.length)])
36
+ );
37
+
38
+ const accentColor = getComputedStyle(document.documentElement)
39
+ .getPropertyValue('--color-accent-500')
40
+ .trim();
41
+
42
+ let bgColor = '';
43
+ function readBgColor() {
44
+ const isDark = document.documentElement.classList.contains('dark');
45
+ bgColor = getComputedStyle(document.documentElement)
46
+ .getPropertyValue(isDark ? '--color-base-900' : '--color-base-50')
47
+ .trim();
48
+ }
49
+ readBgColor();
50
+
51
+ const themeObserver = new MutationObserver(readBgColor);
52
+ themeObserver.observe(document.documentElement, {
53
+ attributes: true,
54
+ attributeFilter: ['class']
55
+ });
56
+
57
+ let lastResize = canvas.width;
58
+ let lastTime = performance.now();
59
+
60
+ function draw(now: number) {
61
+ const dt = Math.min((now - lastTime) / 1000, 0.1); // delta in seconds, capped
62
+ lastTime = now;
63
+
64
+ const w = canvas!.width;
65
+ const h = canvas!.height;
66
+
67
+ if (w !== lastResize) {
68
+ lastResize = w;
69
+ columns = Math.floor(w / fontSize);
70
+ rows = Math.ceil(h / fontSize) + 1;
71
+ drops = new Array(columns).fill(0).map(() => Math.random() * -100);
72
+ grid = Array.from({ length: columns }, () =>
73
+ Array.from({ length: rows }, () => chars[Math.floor(Math.random() * chars.length)])
74
+ );
75
+ }
76
+
77
+ // Randomly mutate characters — framerate independent
78
+ const mutatePerFrame = MUTATE_CHANCE * dt;
79
+ for (let m = 0; m < columns; m++) {
80
+ if (Math.random() < mutatePerFrame) {
81
+ const row = Math.floor(Math.random() * rows);
82
+ grid[m][row] = chars[Math.floor(Math.random() * chars.length)];
83
+ }
84
+ }
85
+
86
+ // Fade trail — framerate independent
87
+ const fadeAlpha = Math.min(1, FADE_RATE * dt);
88
+ ctx.fillStyle = bgColor ? `oklch(from ${bgColor} l c h / ${fadeAlpha})` : `rgba(0, 0, 0, ${fadeAlpha})`;
89
+ ctx.fillRect(0, 0, w, h);
90
+
91
+ ctx.font = `${fontSize}px monospace`;
92
+
93
+ const dropStep = DROP_SPEED * dt;
94
+
95
+ for (let i = 0; i < columns; i++) {
96
+ if (drops[i] * fontSize > h && Math.random() < 0.5 * dt) {
97
+ drops[i] = 0;
98
+ }
99
+
100
+ if (drops[i] < 0) {
101
+ drops[i] += dropStep;
102
+ continue;
103
+ }
104
+
105
+ const row = Math.floor(drops[i]);
106
+ const y = row * fontSize;
107
+ const gridRow = ((row % rows) + rows) % rows;
108
+ const char = grid[i]?.[gridRow] ?? '0';
109
+
110
+ // Bright head
111
+ ctx.fillStyle = accentColor
112
+ ? `oklch(from ${accentColor} calc(l * 1.3) c h / 0.9)`
113
+ : `rgba(150, 255, 150, 0.9)`;
114
+ ctx.fillText(char, i * fontSize, y);
115
+
116
+ // Dimmer trail chars
117
+ for (let t = 1; t < 3; t++) {
118
+ const trailRow = ((gridRow - t) % rows + rows) % rows;
119
+ const trailY = y - t * fontSize;
120
+ if (trailY < 0) break;
121
+ const trailAlpha = 0.4 - t * 0.12;
122
+ ctx.fillStyle = accentColor
123
+ ? `oklch(from ${accentColor} l c h / ${trailAlpha})`
124
+ : `rgba(100, 200, 100, ${trailAlpha})`;
125
+ ctx.fillText(grid[i]?.[trailRow] ?? '0', i * fontSize, trailY);
126
+ }
127
+
128
+ drops[i] += dropStep;
129
+ }
130
+
131
+ animId = requestAnimationFrame(draw);
132
+ }
133
+
134
+ // Fill initial background
135
+ ctx.fillStyle = bgColor ? `oklch(from ${bgColor} l c h)` : '#000';
136
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
137
+
138
+ animId = requestAnimationFrame(draw);
139
+
140
+ return () => {
141
+ cancelAnimationFrame(animId);
142
+ window.removeEventListener('resize', resize);
143
+ themeObserver.disconnect();
144
+ };
145
+ });
146
+ </script>
147
+
148
+ <div class="pointer-events-none fixed inset-0 -z-10 bg-base-50 dark:bg-base-900">
149
+ <canvas bind:this={canvas} class="absolute inset-0 h-full w-full opacity-40"></canvas>
150
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const Matrix: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type Matrix = ReturnType<typeof Matrix>;
3
+ export default Matrix;
@@ -0,0 +1,98 @@
1
+ <script lang="ts">
2
+ import { BROWSER as browser } from 'esm-env';
3
+
4
+ let canvas: HTMLCanvasElement | undefined = $state(undefined);
5
+
6
+ $effect(() => {
7
+ if (!canvas || !browser) return;
8
+
9
+ const ctx = canvas.getContext('2d')!;
10
+ let animId: number;
11
+
12
+ const stars: { x: number; y: number; z: number; pz: number }[] = [];
13
+ const COUNT = 600;
14
+ const SPEED = 300; // pixels per second
15
+
16
+ let lastWidth = 0;
17
+ function resize() {
18
+ const w = window.innerWidth;
19
+ if (w === lastWidth) return;
20
+ lastWidth = w;
21
+ canvas!.width = w;
22
+ canvas!.height = window.screen.height;
23
+ }
24
+ resize();
25
+ window.addEventListener('resize', resize);
26
+
27
+ for (let i = 0; i < COUNT; i++) {
28
+ stars.push({
29
+ x: (Math.random() - 0.5) * canvas.width,
30
+ y: (Math.random() - 0.5) * canvas.height,
31
+ z: Math.random() * canvas.width,
32
+ pz: 0
33
+ });
34
+ }
35
+ stars.forEach((s) => (s.pz = s.z));
36
+
37
+ const accentColor = getComputedStyle(document.documentElement)
38
+ .getPropertyValue('--color-accent-500')
39
+ .trim();
40
+
41
+ let lastTime = performance.now();
42
+
43
+ function draw(now: number) {
44
+ const dt = Math.min((now - lastTime) / 1000, 0.1);
45
+ lastTime = now;
46
+
47
+ const w = canvas!.width;
48
+ const h = canvas!.height;
49
+ const cx = w / 2;
50
+ const cy = h / 2;
51
+ const speed = SPEED * dt;
52
+
53
+ ctx.clearRect(0, 0, w, h);
54
+
55
+ for (const star of stars) {
56
+ star.pz = star.z;
57
+ star.z -= speed;
58
+
59
+ if (star.z <= 0) {
60
+ star.x = (Math.random() - 0.5) * w;
61
+ star.y = (Math.random() - 0.5) * h;
62
+ star.z = w;
63
+ star.pz = w;
64
+ }
65
+
66
+ const sx = (star.x / star.z) * w + cx;
67
+ const sy = (star.y / star.z) * h + cy;
68
+ const px = (star.x / star.pz) * w + cx;
69
+ const py = (star.y / star.pz) * h + cy;
70
+
71
+ const size = Math.max(0, (1 - star.z / w) * 4);
72
+ const alpha = Math.max(0, (1 - star.z / w) * 0.9);
73
+
74
+ ctx.beginPath();
75
+ ctx.moveTo(px, py);
76
+ ctx.lineTo(sx, sy);
77
+ ctx.strokeStyle = accentColor
78
+ ? `oklch(from ${accentColor} l c h / ${alpha})`
79
+ : `rgba(255,255,255,${alpha})`;
80
+ ctx.lineWidth = size;
81
+ ctx.stroke();
82
+ }
83
+
84
+ animId = requestAnimationFrame(draw);
85
+ }
86
+
87
+ animId = requestAnimationFrame(draw);
88
+
89
+ return () => {
90
+ cancelAnimationFrame(animId);
91
+ window.removeEventListener('resize', resize);
92
+ };
93
+ });
94
+ </script>
95
+
96
+ <div class="pointer-events-none fixed inset-0 -z-10 bg-base-50 dark:bg-base-900">
97
+ <canvas bind:this={canvas} class="absolute inset-0 h-full w-full opacity-80"></canvas>
98
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const Stars: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type Stars = ReturnType<typeof Stars>;
3
+ export default Stars;
@@ -0,0 +1,18 @@
1
+ export type ThumbnailRenderer = (ctx: CanvasRenderingContext2D, w: number, h: number, name: string, dateStr: string, seed: number, accent: string) => void;
2
+ export declare const gradientMesh: ThumbnailRenderer;
3
+ export declare const boldType: ThumbnailRenderer;
4
+ export declare const minimal: ThumbnailRenderer;
5
+ export declare const geometric: ThumbnailRenderer;
6
+ export declare const darkGradient: ThumbnailRenderer;
7
+ export declare const waves: ThumbnailRenderer;
8
+ /** Textless gradient-mesh preset — built from 6 overlapping radial hotspots
9
+ * placed around a loose 3×2 grid with seeded jitter. Used as the default
10
+ * thumbnail for new events. */
11
+ export declare const plainMesh: ThumbnailRenderer;
12
+ export declare const DEFAULT_PRESET = "plain";
13
+ export declare const designs: Record<string, ThumbnailRenderer>;
14
+ export declare function resolveAccentColor(name?: string): string;
15
+ /** Stable non-zero integer seed derived from a string (e.g. an event rkey).
16
+ * Same input always produces the same seed, so the picker preview matches
17
+ * the uploaded PNG for a given event. */
18
+ export declare function hashSeed(s: string): number;
@@ -0,0 +1,316 @@
1
+ function lch(accent, l, c = 'c', hShift = 0, a = 1) {
2
+ const lStr = typeof l === 'number' ? l.toFixed(3) : l;
3
+ const cStr = typeof c === 'number' ? c.toFixed(3) : c;
4
+ const hStr = hShift === 0 ? 'h' : `calc(h + ${hShift})`;
5
+ return `oklch(from ${accent} ${lStr} ${cStr} ${hStr} / ${a})`;
6
+ }
7
+ /** Monochrome film-grain overlay. Reads back pixels and perturbs each RGB
8
+ * channel by ±intensity/255 using a seeded LCG — deterministic, so picker
9
+ * previews and the uploaded PNG match. Call as the final step of a design. */
10
+ function addNoise(ctx, w, h, seed, intensity = 7) {
11
+ const img = ctx.getImageData(0, 0, w, h);
12
+ const data = img.data;
13
+ let s = seed | 0 || 1;
14
+ for (let i = 0; i < data.length; i += 4) {
15
+ s = (Math.imul(s, 1664525) + 1013904223) | 0;
16
+ const n = ((s >>> 16) & 0xff) / 255 - 0.5;
17
+ const d = n * 2 * intensity;
18
+ data[i] = Math.max(0, Math.min(255, data[i] + d));
19
+ data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + d));
20
+ data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + d));
21
+ }
22
+ ctx.putImageData(img, 0, 0);
23
+ }
24
+ function drawText(ctx, text, x, y, maxWidth, fontSize, fontWeight, color, align = 'center') {
25
+ ctx.fillStyle = color;
26
+ ctx.font = `${fontWeight} ${fontSize}px system-ui, -apple-system, sans-serif`;
27
+ ctx.textAlign = align;
28
+ ctx.textBaseline = 'middle';
29
+ const words = text.split(' ');
30
+ const lines = [];
31
+ let line = '';
32
+ for (const word of words) {
33
+ const test = line ? `${line} ${word}` : word;
34
+ if (ctx.measureText(test).width > maxWidth && line) {
35
+ lines.push(line);
36
+ line = word;
37
+ }
38
+ else {
39
+ line = test;
40
+ }
41
+ }
42
+ if (line)
43
+ lines.push(line);
44
+ const lineHeight = fontSize * 1.2;
45
+ const totalHeight = lines.length * lineHeight;
46
+ const startY = y - totalHeight / 2 + lineHeight / 2;
47
+ for (let i = 0; i < lines.length; i++) {
48
+ ctx.fillText(lines[i], x, startY + i * lineHeight, maxWidth);
49
+ }
50
+ return totalHeight;
51
+ }
52
+ export const gradientMesh = (ctx, w, h, name, dateStr, seed, accent) => {
53
+ const angle = (seed * 47) % 360;
54
+ const rad = (angle * Math.PI) / 180;
55
+ const cx = w / 2 + (Math.cos(rad) * w) / 2;
56
+ const cy = h / 2 + (Math.sin(rad) * h) / 2;
57
+ const shiftA = ((seed * 37) % 60) - 30;
58
+ const shiftB = ((seed * 71) % 70) + 20;
59
+ const bg = ctx.createLinearGradient(w - cx, h - cy, cx, cy);
60
+ bg.addColorStop(0, lch(accent, 0.55, 'c', shiftA));
61
+ bg.addColorStop(0.5, lch(accent, 0.45, 'c', 0));
62
+ bg.addColorStop(1, lch(accent, 0.35, 'c', -shiftB));
63
+ ctx.fillStyle = bg;
64
+ ctx.fillRect(0, 0, w, h);
65
+ function blob(x, y, r, hShift, alpha) {
66
+ const g = ctx.createRadialGradient(x, y, 0, x, y, r);
67
+ g.addColorStop(0, lch(accent, 0.75, 'c', hShift, alpha));
68
+ g.addColorStop(1, lch(accent, 0.75, 'c', hShift, 0));
69
+ ctx.fillStyle = g;
70
+ ctx.fillRect(x - r, y - r, r * 2, r * 2);
71
+ }
72
+ const p = (i, m) => ((seed * i) % m) / m;
73
+ blob(w * (-0.1 + p(17, 30) * 0.3), h * (-0.1 + p(23, 30) * 0.3), w * 0.4, shiftA * 1.5, 0.45);
74
+ blob(w * (1.1 - p(13, 30) * 0.3), h * (1.1 - p(19, 30) * 0.3), w * 0.35, shiftB, 0.35);
75
+ blob(w * (0.3 + p(29, 40) * 0.4), h * (0.3 + p(31, 40) * 0.4), w * 0.3, -shiftA, 0.3);
76
+ if (name) {
77
+ const th = drawText(ctx, name, w / 2, h / 2 - 10, w * 0.75, w * 0.09, 'bold', 'white');
78
+ if (dateStr) {
79
+ drawText(ctx, dateStr, w / 2, h / 2 + th / 2 + w * 0.03, w * 0.7, w * 0.04, '500', 'rgba(255,255,255,0.85)');
80
+ }
81
+ }
82
+ addNoise(ctx, w, h, seed);
83
+ };
84
+ export const boldType = (ctx, w, h, name, dateStr, seed, accent) => {
85
+ ctx.fillStyle = lch(accent, 0.12, 0.025);
86
+ ctx.fillRect(0, 0, w, h);
87
+ const angle = (seed * 53) % 360;
88
+ const rad = (angle * Math.PI) / 180;
89
+ const hl = ctx.createLinearGradient(w / 2 - (Math.cos(rad) * w) / 2, h / 2 - (Math.sin(rad) * h) / 2, w / 2 + (Math.cos(rad) * w) / 2, h / 2 + (Math.sin(rad) * h) / 2);
90
+ hl.addColorStop(0, lch(accent, 0.22, 'c', 0, 0.4));
91
+ hl.addColorStop(1, lch(accent, 0.1, 0.02, 0, 0));
92
+ ctx.fillStyle = hl;
93
+ ctx.fillRect(0, 0, w, h);
94
+ if (name) {
95
+ drawText(ctx, name, w * 0.07, h * 0.72, w * 0.86, w * 0.11, '900', lch(accent, 0.75, 'c'), 'left');
96
+ }
97
+ if (dateStr) {
98
+ ctx.fillStyle = lch(accent, 0.55, 0.03, 0, 0.9);
99
+ ctx.font = `500 ${w * 0.04}px system-ui, -apple-system, sans-serif`;
100
+ ctx.textAlign = 'left';
101
+ ctx.textBaseline = 'top';
102
+ ctx.fillText(dateStr, w * 0.07, h * 0.88);
103
+ }
104
+ addNoise(ctx, w, h, seed);
105
+ };
106
+ export const minimal = (ctx, w, h, name, dateStr, seed, accent) => {
107
+ ctx.fillStyle = lch(accent, 0.97, 0.02);
108
+ ctx.fillRect(0, 0, w, h);
109
+ const edge = seed % 4;
110
+ ctx.fillStyle = lch(accent, 0.55, 'c');
111
+ if (edge === 0)
112
+ ctx.fillRect(0, h * 0.5 - 1.5, w * 0.12, 3);
113
+ else if (edge === 1)
114
+ ctx.fillRect(w * 0.88, h * 0.5 - 1.5, w * 0.12, 3);
115
+ else if (edge === 2)
116
+ ctx.fillRect(w * 0.44, 0, 3, h * 0.12);
117
+ else
118
+ ctx.fillRect(w * 0.44, h * 0.88, 3, h * 0.12);
119
+ if (name) {
120
+ const th = drawText(ctx, name, w / 2, h / 2 - 10, w * 0.75, w * 0.09, '600', lch(accent, 0.25, 'c'));
121
+ if (dateStr) {
122
+ drawText(ctx, dateStr, w / 2, h / 2 + th / 2 + w * 0.03, w * 0.7, w * 0.04, 'normal', lch(accent, 0.5, 'c'));
123
+ }
124
+ }
125
+ addNoise(ctx, w, h, seed);
126
+ };
127
+ export const geometric = (ctx, w, h, name, dateStr, seed, accent) => {
128
+ ctx.fillStyle = lch(accent, 0.55, 'c');
129
+ ctx.fillRect(0, 0, w, h);
130
+ ctx.globalAlpha = 0.18;
131
+ for (let i = 0; i < 6; i++) {
132
+ const x = (((seed * 31 + i * 73) % 100) / 100) * w;
133
+ const y = (((seed * 47 + i * 59) % 100) / 100) * h;
134
+ const size = ((15 + ((seed * 13 + i * 41) % 25)) / 100) * w;
135
+ const type = i % 3;
136
+ const hShift = (((seed * 19 + i * 53) % 90) - 45) * 0.6;
137
+ ctx.fillStyle = lch(accent, 0.85, 'c', hShift);
138
+ ctx.save();
139
+ ctx.translate(x, y);
140
+ ctx.rotate((((seed * 23 + i * 67) % 360) * Math.PI) / 180);
141
+ if (type === 0) {
142
+ ctx.beginPath();
143
+ ctx.arc(0, 0, size / 2, 0, Math.PI * 2);
144
+ ctx.fill();
145
+ }
146
+ else if (type === 1) {
147
+ ctx.fillRect(-size / 2, -size / 2, size, size);
148
+ }
149
+ else {
150
+ ctx.beginPath();
151
+ ctx.moveTo(0, -size / 2);
152
+ ctx.lineTo(-size / 2, size / 2);
153
+ ctx.lineTo(size / 2, size / 2);
154
+ ctx.closePath();
155
+ ctx.fill();
156
+ }
157
+ ctx.restore();
158
+ }
159
+ ctx.globalAlpha = 1;
160
+ if (name) {
161
+ const th = drawText(ctx, name, w / 2, h / 2 - 10, w * 0.75, w * 0.09, 'bold', 'white');
162
+ if (dateStr) {
163
+ drawText(ctx, dateStr, w / 2, h / 2 + th / 2 + w * 0.03, w * 0.7, w * 0.04, '500', 'rgba(255,255,255,0.8)');
164
+ }
165
+ }
166
+ addNoise(ctx, w, h, seed);
167
+ };
168
+ export const darkGradient = (ctx, w, h, name, dateStr, seed, accent) => {
169
+ const angle = (seed * 67) % 360;
170
+ const rad = (angle * Math.PI) / 180;
171
+ const bg = ctx.createLinearGradient(w / 2 - (Math.cos(rad) * w) / 2, h / 2 - (Math.sin(rad) * h) / 2, w / 2 + (Math.cos(rad) * w) / 2, h / 2 + (Math.sin(rad) * h) / 2);
172
+ bg.addColorStop(0, lch(accent, 0.18, 'c'));
173
+ bg.addColorStop(1, lch(accent, 0.06, 0.03));
174
+ ctx.fillStyle = bg;
175
+ ctx.fillRect(0, 0, w, h);
176
+ const edge = seed % 4;
177
+ const shift = ((seed * 43) % 120) - 60;
178
+ const line = ctx.createLinearGradient(0, 0, w, 0);
179
+ line.addColorStop(0, lch(accent, 0.65, 'c', shift));
180
+ line.addColorStop(1, lch(accent, 0.7, 'c', -shift));
181
+ ctx.fillStyle = line;
182
+ if (edge === 0)
183
+ ctx.fillRect(0, 0, w, h * 0.012);
184
+ else if (edge === 1)
185
+ ctx.fillRect(0, h * 0.988, w, h * 0.012);
186
+ else if (edge === 2)
187
+ ctx.fillRect(0, 0, w * 0.012, h);
188
+ else
189
+ ctx.fillRect(w * 0.988, 0, w * 0.012, h);
190
+ if (name) {
191
+ drawText(ctx, name, w * 0.07, h * 0.72, w * 0.86, w * 0.09, 'bold', 'white', 'left');
192
+ }
193
+ if (dateStr) {
194
+ ctx.fillStyle = lch(accent, 0.65, 'c');
195
+ ctx.font = `normal ${w * 0.04}px system-ui, -apple-system, sans-serif`;
196
+ ctx.textAlign = 'left';
197
+ ctx.textBaseline = 'top';
198
+ ctx.fillText(dateStr, w * 0.07, h * 0.88);
199
+ }
200
+ addNoise(ctx, w, h, seed);
201
+ };
202
+ export const waves = (ctx, w, h, name, dateStr, seed, accent) => {
203
+ ctx.fillStyle = lch(accent, 0.95, 0.04);
204
+ ctx.fillRect(0, 0, w, h);
205
+ function wave(yBase, amplitude, frequency, phase, color, alpha) {
206
+ ctx.globalAlpha = alpha;
207
+ ctx.fillStyle = color;
208
+ ctx.beginPath();
209
+ ctx.moveTo(0, yBase);
210
+ for (let x = 0; x <= w; x += 2) {
211
+ const y = yBase + Math.sin((x / w) * Math.PI * frequency + phase) * amplitude;
212
+ ctx.lineTo(x, y);
213
+ }
214
+ ctx.lineTo(w, h);
215
+ ctx.lineTo(0, h);
216
+ ctx.closePath();
217
+ ctx.fill();
218
+ }
219
+ const freq = 2 + (seed % 3);
220
+ const phase = seed;
221
+ wave(h * 0.7, h * 0.05, freq, phase, lch(accent, 0.78, 'c', 20), 0.55);
222
+ wave(h * 0.78, h * 0.04, freq + 1, phase + 1, lch(accent, 0.65, 'c', 0), 0.5);
223
+ wave(h * 0.85, h * 0.03, freq + 2, phase + 2, lch(accent, 0.5, 'c', -20), 0.55);
224
+ ctx.globalAlpha = 1;
225
+ if (name) {
226
+ const th = drawText(ctx, name, w / 2, h * 0.4, w * 0.75, w * 0.09, 'bold', lch(accent, 0.25, 'c'));
227
+ if (dateStr) {
228
+ drawText(ctx, dateStr, w / 2, h * 0.4 + th / 2 + w * 0.03, w * 0.7, w * 0.04, '500', lch(accent, 0.45, 'c'));
229
+ }
230
+ }
231
+ addNoise(ctx, w, h, seed);
232
+ };
233
+ /** Textless gradient-mesh preset — built from 6 overlapping radial hotspots
234
+ * placed around a loose 3×2 grid with seeded jitter. Used as the default
235
+ * thumbnail for new events. */
236
+ export const plainMesh = (ctx, w, h, _name, _dateStr, seed, accent) => {
237
+ const p = (i, m) => ((seed * i) % m) / m;
238
+ const jitter = (i, amt) => (p(i, 100) - 0.5) * amt;
239
+ // Mid-tone accent base so the hotspots read as both lighter and darker pools
240
+ ctx.fillStyle = lch(accent, 0.5, 'c');
241
+ ctx.fillRect(0, 0, w, h);
242
+ // Six hotspots anchored to a 3x2 grid, each jittered and hue-shifted
243
+ // independently so no two thumbnails look the same.
244
+ const anchors = [
245
+ { x: 0.18, y: 0.22 },
246
+ { x: 0.55, y: 0.15 },
247
+ { x: 0.85, y: 0.35 },
248
+ { x: 0.2, y: 0.72 },
249
+ { x: 0.55, y: 0.85 },
250
+ { x: 0.88, y: 0.78 }
251
+ ];
252
+ // Alternating light/dark levels and hue shifts spread across the spectrum
253
+ const levels = [0.8, 0.62, 0.78, 0.38, 0.72, 0.48];
254
+ const shifts = [-55, 25, 55, -25, 40, -40];
255
+ for (let i = 0; i < anchors.length; i++) {
256
+ const a = anchors[i];
257
+ const x = (a.x + jitter(11 + i * 7, 0.18)) * w;
258
+ const y = (a.y + jitter(17 + i * 11, 0.18)) * h;
259
+ const r = w * (0.45 + p(23 + i * 5, 40) * 0.25);
260
+ const hShift = shifts[i] + jitter(29 + i * 13, 20);
261
+ const l = levels[i] + jitter(31 + i * 3, 0.08);
262
+ const alpha = 0.55 + p(37 + i * 3, 20) * 0.1;
263
+ const g = ctx.createRadialGradient(x, y, 0, x, y, r);
264
+ g.addColorStop(0, lch(accent, l, 'c', hShift, alpha));
265
+ g.addColorStop(1, lch(accent, l, 'c', hShift, 0));
266
+ ctx.fillStyle = g;
267
+ ctx.fillRect(0, 0, w, h);
268
+ }
269
+ addNoise(ctx, w, h, seed);
270
+ };
271
+ export const DEFAULT_PRESET = 'plain';
272
+ export const designs = {
273
+ plain: plainMesh,
274
+ gradient: gradientMesh,
275
+ bold: boldType,
276
+ minimal,
277
+ geometric,
278
+ dark: darkGradient,
279
+ waves
280
+ };
281
+ /** Concrete oklch() values for each Tailwind accent-500, so canvas rendering
282
+ * stays in sync with the theme without depending on CSS-var resolution timing
283
+ * (also works during SSR and before the theme class is applied). */
284
+ const ACCENT_OKLCH = {
285
+ red: 'oklch(0.637 0.237 25.331)',
286
+ orange: 'oklch(0.705 0.213 47.604)',
287
+ amber: 'oklch(0.769 0.188 70.08)',
288
+ yellow: 'oklch(0.795 0.184 86.047)',
289
+ lime: 'oklch(0.768 0.233 130.85)',
290
+ green: 'oklch(0.723 0.219 149.579)',
291
+ emerald: 'oklch(0.696 0.17 162.48)',
292
+ teal: 'oklch(0.704 0.14 182.503)',
293
+ cyan: 'oklch(0.715 0.143 215.221)',
294
+ sky: 'oklch(0.685 0.169 237.323)',
295
+ blue: 'oklch(0.623 0.214 259.815)',
296
+ indigo: 'oklch(0.585 0.233 277.117)',
297
+ violet: 'oklch(0.606 0.25 292.717)',
298
+ purple: 'oklch(0.627 0.265 303.9)',
299
+ fuchsia: 'oklch(0.667 0.295 322.15)',
300
+ pink: 'oklch(0.656 0.241 354.308)',
301
+ rose: 'oklch(0.645 0.246 16.439)'
302
+ };
303
+ export function resolveAccentColor(name) {
304
+ return (name && ACCENT_OKLCH[name]) || ACCENT_OKLCH.cyan;
305
+ }
306
+ /** Stable non-zero integer seed derived from a string (e.g. an event rkey).
307
+ * Same input always produces the same seed, so the picker preview matches
308
+ * the uploaded PNG for a given event. */
309
+ export function hashSeed(s) {
310
+ let h = 2166136261;
311
+ for (let i = 0; i < s.length; i++) {
312
+ h ^= s.charCodeAt(i);
313
+ h = Math.imul(h, 16777619);
314
+ }
315
+ return Math.abs(h) || 1;
316
+ }
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "@atmo-dev/events-ui",
3
+ "version": "0.1.0",
4
+ "description": "Svelte components for atmo events (EventView, EventEditor, EventCard).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/flo-bit/atmo-events.git",
10
+ "directory": "packages/ui"
11
+ },
12
+ "homepage": "https://github.com/flo-bit/atmo-events/tree/main/packages/ui#readme",
13
+ "bugs": "https://github.com/flo-bit/atmo-events/issues",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "scripts": {
18
+ "package": "svelte-kit sync && svelte-package -i src -o dist && publint",
19
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
20
+ "prepublishOnly": "pnpm package"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "!dist/**/*.test.*",
25
+ "!dist/**/*.spec.*"
26
+ ],
27
+ "sideEffects": [
28
+ "**/*.css"
29
+ ],
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "svelte": "./dist/index.js"
34
+ }
35
+ },
36
+ "svelte": "./dist/index.js",
37
+ "peerDependencies": {
38
+ "@foxui/core": "^0.9.0",
39
+ "@foxui/social": "^0.8.0",
40
+ "@foxui/text": "^0.8.0",
41
+ "@foxui/time": "^0.8.0",
42
+ "@foxui/visual": "^0.8.0",
43
+ "bits-ui": "^2.16.0",
44
+ "svelte": "^5.0.0",
45
+ "tailwindcss": "^4.0.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "maplibre-gl": {
49
+ "optional": true
50
+ },
51
+ "svelte-maplibre-gl": {
52
+ "optional": true
53
+ },
54
+ "hls.js": {
55
+ "optional": true
56
+ },
57
+ "plyr": {
58
+ "optional": true
59
+ }
60
+ },
61
+ "dependencies": {
62
+ "@atcute/bluesky-richtext-parser": "^2.1.1",
63
+ "@atcute/tid": "^1.1.2",
64
+ "@atmo-dev/contrail-lexicons": "^0.4.4",
65
+ "@internationalized/date": "^3.12.0",
66
+ "@number-flow/svelte": "^0.4.0",
67
+ "dompurify": "^3.3.3",
68
+ "esm-env": "^1.2.2",
69
+ "marked": "^17.0.5",
70
+ "svelte-boring-avatars": "^1.2.6",
71
+ "svelte-tiptap": "^3.0.1",
72
+ "valibot": "^1.3.1"
73
+ },
74
+ "devDependencies": {
75
+ "@foxui/core": "^0.9.1",
76
+ "@foxui/social": "^0.8.10",
77
+ "@foxui/text": "^0.8.5",
78
+ "@foxui/time": "^0.8.5",
79
+ "@foxui/visual": "^0.8.7",
80
+ "@sveltejs/kit": "^2.55.0",
81
+ "@sveltejs/package": "^2.5.5",
82
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
83
+ "bits-ui": "^2.16.3",
84
+ "hls.js": "^1.6.15",
85
+ "maplibre-gl": "^5.21.1",
86
+ "plyr": "^3.8.4",
87
+ "publint": "^0.3.16",
88
+ "svelte": "^5.55.0",
89
+ "svelte-check": "^4.4.8",
90
+ "svelte-maplibre-gl": "^1.0.3",
91
+ "tailwindcss": "^4.2.2",
92
+ "typescript": "^6.0.2",
93
+ "vite": "^8.0.3"
94
+ }
95
+ }