@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.
- package/dist/DatePicker.svelte +231 -0
- package/dist/DatePicker.svelte.d.ts +11 -0
- package/dist/DateTimePicker.svelte +101 -0
- package/dist/DateTimePicker.svelte.d.ts +9 -0
- package/dist/EventAttendees.svelte +203 -0
- package/dist/EventAttendees.svelte.d.ts +13 -0
- package/dist/EventCard.svelte +131 -0
- package/dist/EventCard.svelte.d.ts +8 -0
- package/dist/EventComments.svelte +99 -0
- package/dist/EventComments.svelte.d.ts +6 -0
- package/dist/EventEditor.svelte +589 -0
- package/dist/EventEditor.svelte.d.ts +20 -0
- package/dist/EventRsvp.svelte +237 -0
- package/dist/EventRsvp.svelte.d.ts +17 -0
- package/dist/EventView.svelte +433 -0
- package/dist/EventView.svelte.d.ts +16 -0
- package/dist/ImageDropper.svelte +66 -0
- package/dist/ImageDropper.svelte.d.ts +7 -0
- package/dist/Map.svelte +27 -0
- package/dist/Map.svelte.d.ts +8 -0
- package/dist/PostToBlueskyModal.svelte +244 -0
- package/dist/PostToBlueskyModal.svelte.d.ts +22 -0
- package/dist/ShareModal.svelte +160 -0
- package/dist/ShareModal.svelte.d.ts +23 -0
- package/dist/ThemeApply.svelte +50 -0
- package/dist/ThemeApply.svelte.d.ts +7 -0
- package/dist/ThemeBackground.svelte +33 -0
- package/dist/ThemeBackground.svelte.d.ts +7 -0
- package/dist/ThemePicker.svelte +102 -0
- package/dist/ThemePicker.svelte.d.ts +7 -0
- package/dist/ThumbnailPresets.svelte +68 -0
- package/dist/ThumbnailPresets.svelte.d.ts +11 -0
- package/dist/TimePicker.svelte +188 -0
- package/dist/TimePicker.svelte.d.ts +9 -0
- package/dist/TimezonePicker.svelte +132 -0
- package/dist/TimezonePicker.svelte.d.ts +6 -0
- package/dist/VodPlayer.svelte +137 -0
- package/dist/VodPlayer.svelte.d.ts +14 -0
- package/dist/VodTranscript.svelte +72 -0
- package/dist/VodTranscript.svelte.d.ts +8 -0
- package/dist/atproto-helpers.d.ts +21 -0
- package/dist/atproto-helpers.js +61 -0
- package/dist/cal/helper.d.ts +1 -0
- package/dist/cal/helper.js +20 -0
- package/dist/cal/ical.d.ts +22 -0
- package/dist/cal/ical.js +188 -0
- package/dist/cal/sanitize.d.ts +3 -0
- package/dist/cal/sanitize.js +25 -0
- package/dist/contrail.d.ts +54 -0
- package/dist/contrail.js +22 -0
- package/dist/date-format.d.ts +22 -0
- package/dist/date-format.js +43 -0
- package/dist/editor/LinksSection.svelte +144 -0
- package/dist/editor/LinksSection.svelte.d.ts +10 -0
- package/dist/editor/LocationSection.svelte +215 -0
- package/dist/editor/LocationSection.svelte.d.ts +8 -0
- package/dist/editor/RecurringModal.svelte +270 -0
- package/dist/editor/RecurringModal.svelte.d.ts +30 -0
- package/dist/editor/ThemeSection.svelte +39 -0
- package/dist/editor/ThemeSection.svelte.d.ts +7 -0
- package/dist/editor/ThumbnailSection.svelte +219 -0
- package/dist/editor/ThumbnailSection.svelte.d.ts +13 -0
- package/dist/editor/adapter.d.ts +98 -0
- package/dist/editor/adapter.js +9 -0
- package/dist/editor/save.d.ts +42 -0
- package/dist/editor/save.js +154 -0
- package/dist/editor/types.d.ts +39 -0
- package/dist/editor/types.js +9 -0
- package/dist/event-types.d.ts +70 -0
- package/dist/event-types.js +11 -0
- package/dist/event-view/AddToCalendarButton.svelte +42 -0
- package/dist/event-view/AddToCalendarButton.svelte.d.ts +9 -0
- package/dist/event-view/EventBadges.svelte +20 -0
- package/dist/event-view/EventBadges.svelte.d.ts +7 -0
- package/dist/event-view/EventDateBlock.svelte +43 -0
- package/dist/event-view/EventDateBlock.svelte.d.ts +7 -0
- package/dist/event-view/EventHostedBy.svelte +63 -0
- package/dist/event-view/EventHostedBy.svelte.d.ts +16 -0
- package/dist/event-view/EventLinksList.svelte +37 -0
- package/dist/event-view/EventLinksList.svelte.d.ts +9 -0
- package/dist/event-view/EventLocationBlock.svelte +48 -0
- package/dist/event-view/EventLocationBlock.svelte.d.ts +7 -0
- package/dist/event-view/EventLocationMap.svelte +72 -0
- package/dist/event-view/EventLocationMap.svelte.d.ts +8 -0
- package/dist/event-view/ExternalRsvpNotice.svelte +44 -0
- package/dist/event-view/ExternalRsvpNotice.svelte.d.ts +6 -0
- package/dist/event-view/InviteShareFlow.svelte +177 -0
- package/dist/event-view/InviteShareFlow.svelte.d.ts +15 -0
- package/dist/event-view/StreamPlacePlayer.svelte +222 -0
- package/dist/event-view/StreamPlacePlayer.svelte.d.ts +8 -0
- package/dist/event-view/format.d.ts +26 -0
- package/dist/event-view/format.js +145 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +18 -0
- package/dist/profile-url.d.ts +1 -0
- package/dist/profile-url.js +7 -0
- package/dist/theme.d.ts +9 -0
- package/dist/theme.js +22 -0
- package/dist/themes/Blobs.svelte +35 -0
- package/dist/themes/Blobs.svelte.d.ts +26 -0
- package/dist/themes/Butterflies.svelte +185 -0
- package/dist/themes/Butterflies.svelte.d.ts +3 -0
- package/dist/themes/Fireflies.svelte +134 -0
- package/dist/themes/Fireflies.svelte.d.ts +3 -0
- package/dist/themes/Kaleidoscope.svelte +177 -0
- package/dist/themes/Kaleidoscope.svelte.d.ts +3 -0
- package/dist/themes/Matrix.svelte +150 -0
- package/dist/themes/Matrix.svelte.d.ts +3 -0
- package/dist/themes/Stars.svelte +98 -0
- package/dist/themes/Stars.svelte.d.ts +3 -0
- package/dist/thumbnails/designs.d.ts +18 -0
- package/dist/thumbnails/designs.js +316 -0
- package/package.json +95 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<div class="pointer-events-none fixed inset-0 -z-10 overflow-hidden bg-base-50 dark:bg-base-900">
|
|
2
|
+
<div
|
|
3
|
+
class="blob-1 absolute rounded-full bg-accent-500 opacity-30 blur-3xl"
|
|
4
|
+
style="width: 40vw; height: 40vw; top: -10%; left: -5%;"
|
|
5
|
+
></div>
|
|
6
|
+
<div
|
|
7
|
+
class="blob-2 absolute rounded-full bg-accent-500 opacity-20 blur-3xl"
|
|
8
|
+
style="width: 35vw; height: 35vw; bottom: -5%; right: -10%;"
|
|
9
|
+
></div>
|
|
10
|
+
<div
|
|
11
|
+
class="blob-3 absolute rounded-full bg-accent-400 opacity-15 blur-3xl"
|
|
12
|
+
style="width: 25vw; height: 25vw; top: 40%; left: 50%;"
|
|
13
|
+
></div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<style>
|
|
17
|
+
@keyframes blob-float-1 {
|
|
18
|
+
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
19
|
+
33% { transform: translate(5vw, 8vh) scale(1.1); }
|
|
20
|
+
66% { transform: translate(-3vw, 4vh) scale(0.95); }
|
|
21
|
+
}
|
|
22
|
+
@keyframes blob-float-2 {
|
|
23
|
+
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
24
|
+
33% { transform: translate(-6vw, -5vh) scale(1.05); }
|
|
25
|
+
66% { transform: translate(4vw, -8vh) scale(1.1); }
|
|
26
|
+
}
|
|
27
|
+
@keyframes blob-float-3 {
|
|
28
|
+
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
29
|
+
33% { transform: translate(8vw, -4vh) scale(1.15); }
|
|
30
|
+
66% { transform: translate(-5vw, 6vh) scale(0.9); }
|
|
31
|
+
}
|
|
32
|
+
.blob-1 { animation: blob-float-1 20s ease-in-out infinite; }
|
|
33
|
+
.blob-2 { animation: blob-float-2 25s ease-in-out infinite; }
|
|
34
|
+
.blob-3 { animation: blob-float-3 22s ease-in-out infinite; }
|
|
35
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default Blobs;
|
|
2
|
+
type Blobs = SvelteComponent<{
|
|
3
|
+
[x: string]: never;
|
|
4
|
+
}, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {}> & {
|
|
7
|
+
$$bindings?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const Blobs: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
+
[x: string]: never;
|
|
11
|
+
}, {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
}, {}, {}, string>;
|
|
14
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
15
|
+
new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
|
|
16
|
+
$$bindings?: Bindings;
|
|
17
|
+
} & Exports;
|
|
18
|
+
(internal: unknown, props: {
|
|
19
|
+
$$events?: Events;
|
|
20
|
+
$$slots?: Slots;
|
|
21
|
+
}): Exports & {
|
|
22
|
+
$set?: any;
|
|
23
|
+
$on?: any;
|
|
24
|
+
};
|
|
25
|
+
z_$$bindings?: Bindings;
|
|
26
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
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 COUNT = 22;
|
|
13
|
+
const DRIFT_SPEED = 45; // px/sec forward flight
|
|
14
|
+
const DART_RATE = 0.15; // darts per second per butterfly
|
|
15
|
+
const ROTATION_EASE = 4; // rad/sec — how fast body turns toward heading
|
|
16
|
+
|
|
17
|
+
interface Butterfly {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
heading: number; // radians — current direction of travel
|
|
21
|
+
rotation: number; // body orientation, lags heading slightly
|
|
22
|
+
turnRate: number; // radians/sec of random-walk steering
|
|
23
|
+
flapPhase: number;
|
|
24
|
+
flapRate: number; // Hz-ish
|
|
25
|
+
depthPhase: number; // slow "closer/further" scaling
|
|
26
|
+
depthRate: number; // Hz of depth oscillation
|
|
27
|
+
size: number;
|
|
28
|
+
hueShift: number;
|
|
29
|
+
alpha: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let lastWidth = 0;
|
|
33
|
+
function resize() {
|
|
34
|
+
const w = window.innerWidth;
|
|
35
|
+
if (w === lastWidth) return;
|
|
36
|
+
lastWidth = w;
|
|
37
|
+
canvas!.width = w;
|
|
38
|
+
canvas!.height = window.screen.height;
|
|
39
|
+
}
|
|
40
|
+
resize();
|
|
41
|
+
window.addEventListener('resize', resize);
|
|
42
|
+
|
|
43
|
+
function spawn(): Butterfly {
|
|
44
|
+
const angle = Math.random() * Math.PI * 2;
|
|
45
|
+
return {
|
|
46
|
+
x: Math.random() * canvas!.width,
|
|
47
|
+
y: Math.random() * canvas!.height,
|
|
48
|
+
heading: angle,
|
|
49
|
+
rotation: angle,
|
|
50
|
+
turnRate: 0.8 + Math.random() * 1.2, // 0.8–2 rad/√s of wander
|
|
51
|
+
flapPhase: Math.random() * Math.PI * 2,
|
|
52
|
+
flapRate: 1.5 + Math.random() * 1.5, // 1.5–3 flaps/sec
|
|
53
|
+
depthPhase: Math.random() * Math.PI * 2,
|
|
54
|
+
depthRate: 0.08 + Math.random() * 0.08, // 1/12s to 1/6s — 6–12s period
|
|
55
|
+
size: 10 + Math.random() * 18,
|
|
56
|
+
hueShift: (Math.random() - 0.5) * 40,
|
|
57
|
+
alpha: 0.35 + Math.random() * 0.4
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const butterflies: Butterfly[] = Array.from({ length: COUNT }, spawn);
|
|
62
|
+
|
|
63
|
+
const accentColor = getComputedStyle(document.documentElement)
|
|
64
|
+
.getPropertyValue('--color-accent-500')
|
|
65
|
+
.trim();
|
|
66
|
+
|
|
67
|
+
// Right wing: top + bottom lobe drawn as a single path so their overlap
|
|
68
|
+
// fills once (no alpha accumulation). Positioned so the inner edge sits
|
|
69
|
+
// at +x ≈ 0.05, keeping the two wings from bleeding into each other at
|
|
70
|
+
// the body axis.
|
|
71
|
+
function drawWing(s: number) {
|
|
72
|
+
ctx.beginPath();
|
|
73
|
+
ctx.ellipse(s * 0.65, -s * 0.3, s * 0.6, s * 0.5, -0.25, 0, Math.PI * 2);
|
|
74
|
+
ctx.ellipse(s * 0.55, s * 0.35, s * 0.5, s * 0.4, 0.2, 0, Math.PI * 2);
|
|
75
|
+
ctx.fill();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function drawButterfly(b: Butterfly) {
|
|
79
|
+
// Smooth 0→1 flap at the natural flapRate (Hz)
|
|
80
|
+
const flap = (Math.sin(b.flapPhase) + 1) / 2;
|
|
81
|
+
const wingScaleX = 0.45 + 0.55 * flap; // 0.45 (folded) → 1.0 (open)
|
|
82
|
+
|
|
83
|
+
// Combined scale: slow "depth" breathing (±25% over ~10s) plus a tiny
|
|
84
|
+
// bump synced to each wing flap so peak-open wings read as closer.
|
|
85
|
+
const depth = 1 + 0.25 * Math.sin(b.depthPhase);
|
|
86
|
+
const flapBump = 1 + 0.08 * flap;
|
|
87
|
+
const scale = depth * flapBump;
|
|
88
|
+
|
|
89
|
+
ctx.save();
|
|
90
|
+
ctx.translate(b.x, b.y);
|
|
91
|
+
ctx.scale(scale, scale);
|
|
92
|
+
ctx.rotate(b.rotation + Math.PI / 2); // body points along velocity
|
|
93
|
+
|
|
94
|
+
const fill = accentColor
|
|
95
|
+
? `oklch(from ${accentColor} l c calc(h + ${b.hueShift}) / ${b.alpha})`
|
|
96
|
+
: `rgba(80, 140, 240, ${b.alpha})`;
|
|
97
|
+
ctx.fillStyle = fill;
|
|
98
|
+
|
|
99
|
+
// Right wing
|
|
100
|
+
ctx.save();
|
|
101
|
+
ctx.scale(wingScaleX, 1);
|
|
102
|
+
drawWing(b.size);
|
|
103
|
+
ctx.restore();
|
|
104
|
+
|
|
105
|
+
// Left wing (mirrored)
|
|
106
|
+
ctx.save();
|
|
107
|
+
ctx.scale(-wingScaleX, 1);
|
|
108
|
+
drawWing(b.size);
|
|
109
|
+
ctx.restore();
|
|
110
|
+
|
|
111
|
+
// Tiny body — slightly darker tint of the accent
|
|
112
|
+
ctx.fillStyle = accentColor
|
|
113
|
+
? `oklch(from ${accentColor} calc(l * 0.55) c calc(h + ${b.hueShift}) / ${Math.min(1, b.alpha + 0.2)})`
|
|
114
|
+
: `rgba(30, 40, 80, ${Math.min(1, b.alpha + 0.2)})`;
|
|
115
|
+
ctx.beginPath();
|
|
116
|
+
ctx.ellipse(0, 0, b.size * 0.08, b.size * 0.55, 0, 0, Math.PI * 2);
|
|
117
|
+
ctx.fill();
|
|
118
|
+
|
|
119
|
+
ctx.restore();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let lastTime = performance.now();
|
|
123
|
+
|
|
124
|
+
function draw(now: number) {
|
|
125
|
+
const dt = Math.min((now - lastTime) / 1000, 0.1);
|
|
126
|
+
lastTime = now;
|
|
127
|
+
|
|
128
|
+
const w = canvas!.width;
|
|
129
|
+
const h = canvas!.height;
|
|
130
|
+
|
|
131
|
+
ctx.clearRect(0, 0, w, h);
|
|
132
|
+
|
|
133
|
+
// Framerate-independent turn-toward-heading easing factor.
|
|
134
|
+
const rotationBlend = 1 - Math.exp(-ROTATION_EASE * dt);
|
|
135
|
+
const sqrtDt = Math.sqrt(dt);
|
|
136
|
+
|
|
137
|
+
for (const b of butterflies) {
|
|
138
|
+
b.flapPhase += b.flapRate * 2 * Math.PI * dt;
|
|
139
|
+
b.depthPhase += b.depthRate * 2 * Math.PI * dt;
|
|
140
|
+
|
|
141
|
+
// Heading random walk — sqrt(dt) keeps the per-second angular
|
|
142
|
+
// variance constant regardless of framerate (Brownian scaling).
|
|
143
|
+
b.heading += (Math.random() - 0.5) * 2 * b.turnRate * sqrtDt;
|
|
144
|
+
|
|
145
|
+
// Occasional sharp dart, as a Poisson process at DART_RATE/sec.
|
|
146
|
+
if (Math.random() < DART_RATE * dt) {
|
|
147
|
+
b.heading += (Math.random() - 0.5) * Math.PI;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
b.x += Math.cos(b.heading) * DRIFT_SPEED * dt;
|
|
151
|
+
b.y += Math.sin(b.heading) * DRIFT_SPEED * dt;
|
|
152
|
+
|
|
153
|
+
// Body orientation eases toward heading with constant time constant.
|
|
154
|
+
let delta = b.heading - b.rotation;
|
|
155
|
+
while (delta > Math.PI) delta -= Math.PI * 2;
|
|
156
|
+
while (delta < -Math.PI) delta += Math.PI * 2;
|
|
157
|
+
b.rotation += delta * rotationBlend;
|
|
158
|
+
|
|
159
|
+
const margin = b.size * 2;
|
|
160
|
+
if (b.x < -margin) b.x = w + margin;
|
|
161
|
+
if (b.x > w + margin) b.x = -margin;
|
|
162
|
+
if (b.y < -margin) b.y = h + margin;
|
|
163
|
+
if (b.y > h + margin) b.y = -margin;
|
|
164
|
+
|
|
165
|
+
drawButterfly(b);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
animId = requestAnimationFrame(draw);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
animId = requestAnimationFrame(draw);
|
|
172
|
+
|
|
173
|
+
return () => {
|
|
174
|
+
cancelAnimationFrame(animId);
|
|
175
|
+
window.removeEventListener('resize', resize);
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
</script>
|
|
179
|
+
|
|
180
|
+
<div class="bg-base-50 dark:bg-base-900 pointer-events-none fixed inset-0 -z-10">
|
|
181
|
+
<canvas
|
|
182
|
+
bind:this={canvas}
|
|
183
|
+
class="absolute inset-0 h-full w-full opacity-60 blur-[1.5px]"
|
|
184
|
+
></canvas>
|
|
185
|
+
</div>
|
|
@@ -0,0 +1,134 @@
|
|
|
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 COUNT = 50;
|
|
13
|
+
const DRIFT_SPEED = 15;
|
|
14
|
+
const PULSE_SPEED = 0.6;
|
|
15
|
+
|
|
16
|
+
interface Firefly {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
vx: number;
|
|
20
|
+
vy: number;
|
|
21
|
+
phase: number;
|
|
22
|
+
pulseRate: number;
|
|
23
|
+
size: number;
|
|
24
|
+
hueShift: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let lastWidth = 0;
|
|
28
|
+
function resize() {
|
|
29
|
+
const w = window.innerWidth;
|
|
30
|
+
if (w === lastWidth) return;
|
|
31
|
+
lastWidth = w;
|
|
32
|
+
canvas!.width = w;
|
|
33
|
+
canvas!.height = window.screen.height;
|
|
34
|
+
}
|
|
35
|
+
resize();
|
|
36
|
+
window.addEventListener('resize', resize);
|
|
37
|
+
|
|
38
|
+
const flies: Firefly[] = Array.from({ length: COUNT }, () => ({
|
|
39
|
+
x: Math.random() * canvas!.width,
|
|
40
|
+
y: Math.random() * canvas!.height,
|
|
41
|
+
vx: (Math.random() - 0.5) * 2,
|
|
42
|
+
vy: (Math.random() - 0.5) * 2,
|
|
43
|
+
phase: Math.random() * Math.PI * 2,
|
|
44
|
+
pulseRate: PULSE_SPEED * (0.6 + Math.random() * 0.8),
|
|
45
|
+
size: 3 + Math.random() * 5,
|
|
46
|
+
hueShift: (Math.random() - 0.5) * 60 // -30 to +30 degrees
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
const accentColor = getComputedStyle(document.documentElement)
|
|
50
|
+
.getPropertyValue('--color-accent-500')
|
|
51
|
+
.trim();
|
|
52
|
+
|
|
53
|
+
let lastTime = performance.now();
|
|
54
|
+
|
|
55
|
+
function draw(now: number) {
|
|
56
|
+
const dt = Math.min((now - lastTime) / 1000, 0.1);
|
|
57
|
+
lastTime = now;
|
|
58
|
+
|
|
59
|
+
const w = canvas!.width;
|
|
60
|
+
const h = canvas!.height;
|
|
61
|
+
|
|
62
|
+
ctx.clearRect(0, 0, w, h);
|
|
63
|
+
|
|
64
|
+
for (const fly of flies) {
|
|
65
|
+
fly.phase += fly.pulseRate * dt;
|
|
66
|
+
fly.x += fly.vx * DRIFT_SPEED * dt;
|
|
67
|
+
fly.y += fly.vy * DRIFT_SPEED * dt;
|
|
68
|
+
|
|
69
|
+
fly.vx += (Math.random() - 0.5) * 2 * dt;
|
|
70
|
+
fly.vy += (Math.random() - 0.5) * 2 * dt;
|
|
71
|
+
const len = Math.sqrt(fly.vx * fly.vx + fly.vy * fly.vy);
|
|
72
|
+
if (len > 1) {
|
|
73
|
+
fly.vx /= len;
|
|
74
|
+
fly.vy /= len;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (fly.x < -40) fly.x = w + 40;
|
|
78
|
+
if (fly.x > w + 40) fly.x = -40;
|
|
79
|
+
if (fly.y < -40) fly.y = h + 40;
|
|
80
|
+
if (fly.y > h + 40) fly.y = -40;
|
|
81
|
+
|
|
82
|
+
const glow = (Math.sin(fly.phase) + 1) / 2;
|
|
83
|
+
const alpha = 0.05 + glow * 0.5;
|
|
84
|
+
const radius = fly.size * (0.5 + glow * 0.5);
|
|
85
|
+
const glowRadius = radius * 12;
|
|
86
|
+
const h_shift = fly.hueShift;
|
|
87
|
+
|
|
88
|
+
// Large soft glow
|
|
89
|
+
const gradient = ctx.createRadialGradient(fly.x, fly.y, 0, fly.x, fly.y, glowRadius);
|
|
90
|
+
gradient.addColorStop(
|
|
91
|
+
0,
|
|
92
|
+
accentColor
|
|
93
|
+
? `oklch(from ${accentColor} l c calc(h + ${h_shift}) / ${alpha * 0.4})`
|
|
94
|
+
: `rgba(255, 200, 50, ${alpha * 0.4})`
|
|
95
|
+
);
|
|
96
|
+
gradient.addColorStop(
|
|
97
|
+
0.4,
|
|
98
|
+
accentColor
|
|
99
|
+
? `oklch(from ${accentColor} l c calc(h + ${h_shift}) / ${alpha * 0.15})`
|
|
100
|
+
: `rgba(255, 200, 50, ${alpha * 0.15})`
|
|
101
|
+
);
|
|
102
|
+
gradient.addColorStop(
|
|
103
|
+
1,
|
|
104
|
+
accentColor
|
|
105
|
+
? `oklch(from ${accentColor} l c calc(h + ${h_shift}) / 0)`
|
|
106
|
+
: `rgba(255, 200, 50, 0)`
|
|
107
|
+
);
|
|
108
|
+
ctx.fillStyle = gradient;
|
|
109
|
+
ctx.fillRect(fly.x - glowRadius, fly.y - glowRadius, glowRadius * 2, glowRadius * 2);
|
|
110
|
+
|
|
111
|
+
// Bright core
|
|
112
|
+
ctx.beginPath();
|
|
113
|
+
ctx.arc(fly.x, fly.y, radius, 0, Math.PI * 2);
|
|
114
|
+
ctx.fillStyle = accentColor
|
|
115
|
+
? `oklch(from ${accentColor} calc(l * 1.4) c calc(h + ${h_shift}) / ${alpha})`
|
|
116
|
+
: `rgba(255, 230, 100, ${alpha})`;
|
|
117
|
+
ctx.fill();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
animId = requestAnimationFrame(draw);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
animId = requestAnimationFrame(draw);
|
|
124
|
+
|
|
125
|
+
return () => {
|
|
126
|
+
cancelAnimationFrame(animId);
|
|
127
|
+
window.removeEventListener('resize', resize);
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<div class="bg-base-50 dark:bg-base-900 pointer-events-none fixed inset-0 -z-10">
|
|
133
|
+
<canvas bind:this={canvas} class="absolute inset-0 h-full w-full opacity-50"></canvas>
|
|
134
|
+
</div>
|
|
@@ -0,0 +1,177 @@
|
|
|
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 SEGMENTS = 12;
|
|
13
|
+
const ROTATION_SPEED = 0.06;
|
|
14
|
+
|
|
15
|
+
let lastWidth = 0;
|
|
16
|
+
function resize() {
|
|
17
|
+
const w = window.innerWidth;
|
|
18
|
+
if (w === lastWidth) return;
|
|
19
|
+
lastWidth = w;
|
|
20
|
+
canvas!.width = w;
|
|
21
|
+
canvas!.height = window.screen.height;
|
|
22
|
+
}
|
|
23
|
+
resize();
|
|
24
|
+
window.addEventListener('resize', resize);
|
|
25
|
+
|
|
26
|
+
const accentColor = getComputedStyle(document.documentElement)
|
|
27
|
+
.getPropertyValue('--color-accent-500')
|
|
28
|
+
.trim();
|
|
29
|
+
|
|
30
|
+
// Animated blobs in normalized 0-1 space
|
|
31
|
+
const BLOB_COUNT = 20;
|
|
32
|
+
interface Blob {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
vx: number;
|
|
36
|
+
vy: number;
|
|
37
|
+
size: number;
|
|
38
|
+
hueShift: number;
|
|
39
|
+
alpha: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const blobs: (Blob & { sharp?: boolean })[] = [
|
|
43
|
+
...Array.from({ length: BLOB_COUNT }, (): Blob => ({
|
|
44
|
+
x: Math.random(),
|
|
45
|
+
y: Math.random() * 0.8,
|
|
46
|
+
vx: (Math.random() - 0.5) * 0.04,
|
|
47
|
+
vy: (Math.random() - 0.5) * 0.04,
|
|
48
|
+
size: 0.04 + Math.random() * 0.1,
|
|
49
|
+
hueShift: (Math.random() - 0.5) * 80,
|
|
50
|
+
alpha: 0.15 + Math.random() * 0.35
|
|
51
|
+
})),
|
|
52
|
+
// Small sharp dots
|
|
53
|
+
...Array.from({ length: 10 }, () => ({
|
|
54
|
+
x: Math.random(),
|
|
55
|
+
y: Math.random() * 0.8,
|
|
56
|
+
vx: (Math.random() - 0.5) * 0.06,
|
|
57
|
+
vy: (Math.random() - 0.5) * 0.06,
|
|
58
|
+
size: 0.005 + Math.random() * 0.012,
|
|
59
|
+
hueShift: (Math.random() - 0.5) * 80,
|
|
60
|
+
alpha: 0.15 + Math.random() * 0.2,
|
|
61
|
+
sharp: true
|
|
62
|
+
}))
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// Offscreen canvas for one triangle slice
|
|
66
|
+
const sliceCanvas = document.createElement('canvas');
|
|
67
|
+
const sliceCtx = sliceCanvas.getContext('2d')!;
|
|
68
|
+
|
|
69
|
+
let lastTime = performance.now();
|
|
70
|
+
let globalRotation = 0;
|
|
71
|
+
|
|
72
|
+
function renderSlice(r: number, dt: number) {
|
|
73
|
+
const segAngle = Math.PI / SEGMENTS; // half of full segment
|
|
74
|
+
const sliceW = Math.ceil(r);
|
|
75
|
+
const sliceH = Math.ceil(Math.sin(segAngle) * r);
|
|
76
|
+
|
|
77
|
+
sliceCanvas.width = sliceW;
|
|
78
|
+
sliceCanvas.height = sliceH;
|
|
79
|
+
sliceCtx.clearRect(0, 0, sliceW, sliceH);
|
|
80
|
+
|
|
81
|
+
// Clip to the triangle
|
|
82
|
+
sliceCtx.save();
|
|
83
|
+
sliceCtx.beginPath();
|
|
84
|
+
sliceCtx.moveTo(0, 0);
|
|
85
|
+
sliceCtx.lineTo(sliceW, 0);
|
|
86
|
+
sliceCtx.lineTo(Math.cos(segAngle) * r, Math.sin(segAngle) * r);
|
|
87
|
+
sliceCtx.closePath();
|
|
88
|
+
sliceCtx.clip();
|
|
89
|
+
|
|
90
|
+
for (const b of blobs) {
|
|
91
|
+
b.x += b.vx * dt;
|
|
92
|
+
b.y += b.vy * dt;
|
|
93
|
+
if (b.x < 0 || b.x > 1) b.vx *= -1;
|
|
94
|
+
if (b.y < 0 || b.y > 1) b.vy *= -1;
|
|
95
|
+
b.x = Math.max(0, Math.min(1, b.x));
|
|
96
|
+
b.y = Math.max(0, Math.min(1, b.y));
|
|
97
|
+
|
|
98
|
+
const px = b.x * sliceW;
|
|
99
|
+
const py = b.y * sliceH;
|
|
100
|
+
const br = b.size * sliceW;
|
|
101
|
+
|
|
102
|
+
if (b.sharp) {
|
|
103
|
+
sliceCtx.fillStyle = accentColor
|
|
104
|
+
? `oklch(from ${accentColor} calc(l * 1.4) c calc(h + ${b.hueShift}) / ${b.alpha})`
|
|
105
|
+
: `rgba(230, 210, 255, ${b.alpha})`;
|
|
106
|
+
sliceCtx.beginPath();
|
|
107
|
+
sliceCtx.arc(px, py, br, 0, Math.PI * 2);
|
|
108
|
+
sliceCtx.fill();
|
|
109
|
+
} else {
|
|
110
|
+
const g = sliceCtx.createRadialGradient(px, py, 0, px, py, br);
|
|
111
|
+
g.addColorStop(0, accentColor
|
|
112
|
+
? `oklch(from ${accentColor} calc(l * 1.2) c calc(h + ${b.hueShift}) / ${b.alpha})`
|
|
113
|
+
: `rgba(200, 150, 255, ${b.alpha})`);
|
|
114
|
+
g.addColorStop(0.5, accentColor
|
|
115
|
+
? `oklch(from ${accentColor} l c calc(h + ${b.hueShift}) / ${b.alpha * 0.3})`
|
|
116
|
+
: `rgba(200, 150, 255, ${b.alpha * 0.3})`);
|
|
117
|
+
g.addColorStop(1, 'transparent');
|
|
118
|
+
sliceCtx.fillStyle = g;
|
|
119
|
+
sliceCtx.fillRect(px - br, py - br, br * 2, br * 2);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
sliceCtx.restore();
|
|
124
|
+
return { sliceW, sliceH };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function draw(now: number) {
|
|
128
|
+
const dt = Math.min((now - lastTime) / 1000, 0.1);
|
|
129
|
+
lastTime = now;
|
|
130
|
+
|
|
131
|
+
const w = canvas!.width;
|
|
132
|
+
const h = canvas!.height;
|
|
133
|
+
const cx = w / 2;
|
|
134
|
+
const cy = h / 2;
|
|
135
|
+
const maxR = Math.hypot(cx, cy);
|
|
136
|
+
|
|
137
|
+
globalRotation += ROTATION_SPEED * dt;
|
|
138
|
+
|
|
139
|
+
renderSlice(maxR, dt);
|
|
140
|
+
|
|
141
|
+
ctx.clearRect(0, 0, w, h);
|
|
142
|
+
ctx.save();
|
|
143
|
+
ctx.translate(cx, cy);
|
|
144
|
+
ctx.rotate(globalRotation);
|
|
145
|
+
|
|
146
|
+
const segAngle = (Math.PI * 2) / SEGMENTS;
|
|
147
|
+
|
|
148
|
+
for (let i = 0; i < SEGMENTS; i++) {
|
|
149
|
+
ctx.save();
|
|
150
|
+
ctx.rotate(segAngle * i);
|
|
151
|
+
|
|
152
|
+
// Normal slice
|
|
153
|
+
ctx.drawImage(sliceCanvas, 0, 0);
|
|
154
|
+
|
|
155
|
+
// Mirrored slice (flip along x-axis to fill the other half)
|
|
156
|
+
ctx.scale(1, -1);
|
|
157
|
+
ctx.drawImage(sliceCanvas, 0, 0);
|
|
158
|
+
|
|
159
|
+
ctx.restore();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
ctx.restore();
|
|
163
|
+
animId = requestAnimationFrame(draw);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
animId = requestAnimationFrame(draw);
|
|
167
|
+
|
|
168
|
+
return () => {
|
|
169
|
+
cancelAnimationFrame(animId);
|
|
170
|
+
window.removeEventListener('resize', resize);
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<div class="pointer-events-none fixed inset-0 -z-10 bg-base-50 dark:bg-base-900">
|
|
176
|
+
<canvas bind:this={canvas} class="absolute inset-0 h-full w-full opacity-50"></canvas>
|
|
177
|
+
</div>
|