@gigo-ui/components 1.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.
- package/README.md +42 -0
- package/dist/assets/favicon.svg +1 -0
- package/dist/components/chaos/CatchSubmit.svelte +144 -0
- package/dist/components/chaos/CatchSubmit.svelte.d.ts +11 -0
- package/dist/components/chaos/ChaosButton.svelte +132 -0
- package/dist/components/chaos/ChaosButton.svelte.d.ts +4 -0
- package/dist/components/chaos/ChaosForm.svelte +206 -0
- package/dist/components/chaos/ChaosForm.svelte.d.ts +10 -0
- package/dist/components/chaos/ColorPickerWrong.svelte +141 -0
- package/dist/components/chaos/ColorPickerWrong.svelte.d.ts +11 -0
- package/dist/components/chaos/DropdownCalc.svelte +195 -0
- package/dist/components/chaos/DropdownCalc.svelte.d.ts +8 -0
- package/dist/components/chaos/GhostCard.svelte +157 -0
- package/dist/components/chaos/GhostCard.svelte.d.ts +13 -0
- package/dist/components/chaos/GravityInput.svelte +161 -0
- package/dist/components/chaos/GravityInput.svelte.d.ts +13 -0
- package/dist/components/chaos/PasswordPeekhole.svelte +141 -0
- package/dist/components/chaos/PasswordPeekhole.svelte.d.ts +11 -0
- package/dist/components/chaos/ProgressDoom.svelte +139 -0
- package/dist/components/chaos/ProgressDoom.svelte.d.ts +12 -0
- package/dist/components/chaos/RotaryDial.svelte +221 -0
- package/dist/components/chaos/RotaryDial.svelte.d.ts +10 -0
- package/dist/components/chaos/SliderPhone.svelte +92 -0
- package/dist/components/chaos/SliderPhone.svelte.d.ts +10 -0
- package/dist/components/chaos/TermsSidescroll.svelte +121 -0
- package/dist/components/chaos/TermsSidescroll.svelte.d.ts +12 -0
- package/dist/components/chaos/VolumeSlider.svelte +116 -0
- package/dist/components/chaos/VolumeSlider.svelte.d.ts +14 -0
- package/dist/components/ui/Button.svelte +162 -0
- package/dist/components/ui/Button.svelte.d.ts +4 -0
- package/dist/components/ui/Card.svelte +141 -0
- package/dist/components/ui/Card.svelte.d.ts +4 -0
- package/dist/components/ui/Carousel.svelte +164 -0
- package/dist/components/ui/Carousel.svelte.d.ts +4 -0
- package/dist/components/ui/Form.svelte +178 -0
- package/dist/components/ui/Form.svelte.d.ts +4 -0
- package/dist/components/ui/Input.svelte +135 -0
- package/dist/components/ui/Input.svelte.d.ts +4 -0
- package/dist/components/ui/Modal.svelte +173 -0
- package/dist/components/ui/Modal.svelte.d.ts +4 -0
- package/dist/components/ui/Navigation.svelte +91 -0
- package/dist/components/ui/Navigation.svelte.d.ts +4 -0
- package/dist/docs/categories.d.ts +13 -0
- package/dist/docs/categories.js +17 -0
- package/dist/docs/component-data.d.ts +6 -0
- package/dist/docs/component-data.js +49 -0
- package/dist/docs/components/badui/catch-submit.d.ts +2 -0
- package/dist/docs/components/badui/catch-submit.js +49 -0
- package/dist/docs/components/badui/color-picker-wrong.d.ts +2 -0
- package/dist/docs/components/badui/color-picker-wrong.js +40 -0
- package/dist/docs/components/badui/dropdown-calc.d.ts +2 -0
- package/dist/docs/components/badui/dropdown-calc.js +28 -0
- package/dist/docs/components/badui/ghost-card.d.ts +2 -0
- package/dist/docs/components/badui/ghost-card.js +54 -0
- package/dist/docs/components/badui/gravity-input.d.ts +2 -0
- package/dist/docs/components/badui/gravity-input.js +64 -0
- package/dist/docs/components/badui/password-peekhole.d.ts +2 -0
- package/dist/docs/components/badui/password-peekhole.js +51 -0
- package/dist/docs/components/badui/progress-doom.d.ts +2 -0
- package/dist/docs/components/badui/progress-doom.js +48 -0
- package/dist/docs/components/badui/rotary-dial.d.ts +2 -0
- package/dist/docs/components/badui/rotary-dial.js +40 -0
- package/dist/docs/components/badui/slider-phone.d.ts +2 -0
- package/dist/docs/components/badui/slider-phone.js +40 -0
- package/dist/docs/components/badui/terms-sidescroll.d.ts +2 -0
- package/dist/docs/components/badui/terms-sidescroll.js +46 -0
- package/dist/docs/components/badui/volume-slider.d.ts +2 -0
- package/dist/docs/components/badui/volume-slider.js +52 -0
- package/dist/docs/components/chaos/chaos-button.d.ts +2 -0
- package/dist/docs/components/chaos/chaos-button.js +48 -0
- package/dist/docs/components/chaos/chaos-form.d.ts +2 -0
- package/dist/docs/components/chaos/chaos-form.js +68 -0
- package/dist/docs/components/standard/button.d.ts +2 -0
- package/dist/docs/components/standard/button.js +66 -0
- package/dist/docs/components/standard/card.d.ts +2 -0
- package/dist/docs/components/standard/card.js +55 -0
- package/dist/docs/components/standard/carousel.d.ts +2 -0
- package/dist/docs/components/standard/carousel.js +63 -0
- package/dist/docs/components/standard/form.d.ts +2 -0
- package/dist/docs/components/standard/form.js +57 -0
- package/dist/docs/components/standard/input.d.ts +2 -0
- package/dist/docs/components/standard/input.js +65 -0
- package/dist/docs/components/standard/modal.d.ts +2 -0
- package/dist/docs/components/standard/modal.js +63 -0
- package/dist/docs/components/standard/navigation.d.ts +2 -0
- package/dist/docs/components/standard/navigation.js +51 -0
- package/dist/docs/types.d.ts +16 -0
- package/dist/docs/types.js +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +21 -0
- package/dist/styles/globals.css +569 -0
- package/dist/types/index.d.ts +177 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/cn.d.ts +9 -0
- package/dist/utils/cn.js +90 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# sv
|
|
2
|
+
|
|
3
|
+
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
|
4
|
+
|
|
5
|
+
## Creating a project
|
|
6
|
+
|
|
7
|
+
If you're seeing this, you've probably already done this step. Congrats!
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
# create a new project
|
|
11
|
+
npx sv create my-app
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
To recreate this project with the same configuration:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
# recreate this project
|
|
18
|
+
npx sv@0.12.5 create --template minimal --types ts --no-install .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Developing
|
|
22
|
+
|
|
23
|
+
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npm run dev
|
|
27
|
+
|
|
28
|
+
# or start the server and open the app in a new browser tab
|
|
29
|
+
npm run dev -- --open
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Building
|
|
33
|
+
|
|
34
|
+
To create a production version of your app:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm run build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
You can preview the production build with `npm run preview`.
|
|
41
|
+
|
|
42
|
+
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../utils/cn.js';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
label = 'Submit',
|
|
6
|
+
onsubmit,
|
|
7
|
+
arenaHeight = 256,
|
|
8
|
+
tauntsArray = [
|
|
9
|
+
'Too slow!', 'Almost!', 'Nope!', 'Try harder!', 'Not today!',
|
|
10
|
+
'🏃💨', "Can't catch me!", 'Haha!', 'Over here!', 'Nice try!',
|
|
11
|
+
'So close... not.', 'LOL', 'Git gud', 'Skill issue',
|
|
12
|
+
],
|
|
13
|
+
class: className,
|
|
14
|
+
...restProps
|
|
15
|
+
}: {
|
|
16
|
+
label?: string;
|
|
17
|
+
onsubmit?: () => void;
|
|
18
|
+
arenaHeight?: number;
|
|
19
|
+
tauntsArray?: string[];
|
|
20
|
+
class?: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
let x = $state(0);
|
|
25
|
+
let y = $state(0);
|
|
26
|
+
let escapeCount = $state(0);
|
|
27
|
+
let caught = $state(false);
|
|
28
|
+
let containerEl: HTMLDivElement | undefined = $state();
|
|
29
|
+
let btnEl: HTMLButtonElement | undefined = $state();
|
|
30
|
+
let taunts = $state('');
|
|
31
|
+
let trailDots = $state<Array<{ x: number; y: number }>>([]);
|
|
32
|
+
|
|
33
|
+
function flee() {
|
|
34
|
+
if (caught || !containerEl || !btnEl) return;
|
|
35
|
+
|
|
36
|
+
const container = containerEl.getBoundingClientRect();
|
|
37
|
+
const btn = btnEl.getBoundingClientRect();
|
|
38
|
+
const maxX = container.width - btn.width - 8;
|
|
39
|
+
const maxY = container.height - btn.height - 8;
|
|
40
|
+
|
|
41
|
+
// Random position away from current
|
|
42
|
+
let newX: number, newY: number;
|
|
43
|
+
let attempts = 0;
|
|
44
|
+
do {
|
|
45
|
+
newX = Math.random() * maxX;
|
|
46
|
+
newY = Math.random() * maxY;
|
|
47
|
+
attempts++;
|
|
48
|
+
} while (
|
|
49
|
+
Math.abs(newX - x) < 60 &&
|
|
50
|
+
Math.abs(newY - y) < 60 &&
|
|
51
|
+
attempts < 10
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
x = newX;
|
|
55
|
+
y = newY;
|
|
56
|
+
escapeCount++;
|
|
57
|
+
taunts = tauntsArray[Math.floor(Math.random() * tauntsArray.length)];
|
|
58
|
+
trailDots = [...trailDots, { x: newX + 20, y: newY + 10 }].slice(-20);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handleClick() {
|
|
62
|
+
// Only reachable if they somehow click it
|
|
63
|
+
caught = true;
|
|
64
|
+
taunts = 'Fine, you win. 😤';
|
|
65
|
+
onsubmit?.();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Place button randomly on mount
|
|
69
|
+
$effect(() => {
|
|
70
|
+
if (containerEl && btnEl) {
|
|
71
|
+
const container = containerEl.getBoundingClientRect();
|
|
72
|
+
const btn = btnEl.getBoundingClientRect();
|
|
73
|
+
x = Math.random() * (container.width - btn.width - 8);
|
|
74
|
+
y = Math.random() * (container.height - btn.height - 8);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<div class={cn('space-y-3', className)} {...restProps}>
|
|
80
|
+
<!-- Stats bar -->
|
|
81
|
+
<div class="flex items-center justify-between rounded-lg border border-(--border) bg-(--secondary) px-3 py-1.5 text-xs font-mono">
|
|
82
|
+
<span class="text-(--muted-foreground)">Escape count: <span class="text-gigo-magenta font-bold">{escapeCount}</span></span>
|
|
83
|
+
{#if taunts}
|
|
84
|
+
<span class="text-gigo-cyan animate-pulse">{taunts}</span>
|
|
85
|
+
{/if}
|
|
86
|
+
<span class="text-(--muted-foreground)">
|
|
87
|
+
Status: {caught ? '✅ Caught' : '🏃 Fleeing'}
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- Chase arena -->
|
|
92
|
+
<div
|
|
93
|
+
bind:this={containerEl}
|
|
94
|
+
class="relative w-full rounded-2xl border border-(--border) bg-(--card) overflow-hidden"
|
|
95
|
+
style:height="{arenaHeight}px"
|
|
96
|
+
>
|
|
97
|
+
<!-- Grid background -->
|
|
98
|
+
<div class="absolute inset-0 opacity-[0.03]" style="background-image: linear-gradient(var(--muted-foreground) 1px, transparent 1px), linear-gradient(90deg, var(--muted-foreground) 1px, transparent 1px); background-size: 24px 24px;"></div>
|
|
99
|
+
|
|
100
|
+
<!-- Trail dots -->
|
|
101
|
+
{#if trailDots.length > 0}
|
|
102
|
+
<div class="absolute inset-0 pointer-events-none overflow-hidden">
|
|
103
|
+
{#each trailDots as dot, i}
|
|
104
|
+
<div
|
|
105
|
+
class="absolute w-1.5 h-1.5 rounded-full bg-gigo-magenta/20"
|
|
106
|
+
style:left="{dot.x}px"
|
|
107
|
+
style:top="{dot.y}px"
|
|
108
|
+
style:opacity={0.1 + (i / trailDots.length) * 0.4}
|
|
109
|
+
></div>
|
|
110
|
+
{/each}
|
|
111
|
+
</div>
|
|
112
|
+
{/if}
|
|
113
|
+
|
|
114
|
+
<!-- The elusive button -->
|
|
115
|
+
<button
|
|
116
|
+
bind:this={btnEl}
|
|
117
|
+
class="absolute px-6 py-2.5 rounded-xl font-mono text-sm font-bold
|
|
118
|
+
transition-all duration-150 ease-out select-none cursor-pointer"
|
|
119
|
+
style:left="{x}px"
|
|
120
|
+
style:top="{y}px"
|
|
121
|
+
style:background={caught ? 'rgba(118, 255, 3, 0.2)' : 'rgba(224, 64, 251, 0.15)'}
|
|
122
|
+
style:border="1px solid {caught ? '#76ff03' : '#e040fb'}"
|
|
123
|
+
style:color={caught ? '#76ff03' : '#e040fb'}
|
|
124
|
+
style:box-shadow={caught ? '0 0 20px rgba(118, 255, 3, 0.3)' : '0 0 12px rgba(224, 64, 251, 0.2)'}
|
|
125
|
+
onpointerenter={caught ? undefined : flee}
|
|
126
|
+
onclick={handleClick}
|
|
127
|
+
disabled={caught}
|
|
128
|
+
>
|
|
129
|
+
{caught ? '✓ Submitted!' : label}
|
|
130
|
+
</button>
|
|
131
|
+
|
|
132
|
+
{#if !caught && escapeCount > 5}
|
|
133
|
+
<div class="absolute bottom-3 left-0 right-0 text-center">
|
|
134
|
+
<span class="text-[10px] font-mono text-(--muted-foreground) opacity-50">
|
|
135
|
+
Hint: Try using Tab key 😏
|
|
136
|
+
</span>
|
|
137
|
+
</div>
|
|
138
|
+
{/if}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<p class="text-[10px] font-mono text-(--muted-foreground) text-center">
|
|
142
|
+
Hover over the button to submit. Should be easy... right?
|
|
143
|
+
</p>
|
|
144
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
label?: string;
|
|
3
|
+
onsubmit?: () => void;
|
|
4
|
+
arenaHeight?: number;
|
|
5
|
+
tauntsArray?: string[];
|
|
6
|
+
class?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
declare const CatchSubmit: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type CatchSubmit = ReturnType<typeof CatchSubmit>;
|
|
11
|
+
export default CatchSubmit;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, chaosClasses, chaosRandom, randomGarbageText } from '../../utils/cn.js';
|
|
3
|
+
import type { GigoChaosButtonProps } from '../../types/index.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
label = 'Click me',
|
|
7
|
+
chaos = true,
|
|
8
|
+
chaosLevel = 8,
|
|
9
|
+
educational = false,
|
|
10
|
+
a11yWarning = false,
|
|
11
|
+
onclick,
|
|
12
|
+
class: className,
|
|
13
|
+
...restProps
|
|
14
|
+
}: GigoChaosButtonProps = $props();
|
|
15
|
+
|
|
16
|
+
let posX = $state(0);
|
|
17
|
+
let posY = $state(0);
|
|
18
|
+
let currentLabel = $state('Click me');
|
|
19
|
+
let requiredClicks = $state(1);
|
|
20
|
+
|
|
21
|
+
$effect(() => { currentLabel = label; });
|
|
22
|
+
let clicksSoFar = $state(0);
|
|
23
|
+
let fakeButtons = $state<Array<{ id: number; x: number; y: number; text: string }>>([]);
|
|
24
|
+
let nextFakeId = $state(0);
|
|
25
|
+
|
|
26
|
+
// Teleport every 2 seconds
|
|
27
|
+
$effect(() => {
|
|
28
|
+
const timer = setInterval(() => {
|
|
29
|
+
posX = (Math.random() - 0.5) * 400;
|
|
30
|
+
posY = (Math.random() - 0.5) * 300;
|
|
31
|
+
}, 2000);
|
|
32
|
+
return () => clearInterval(timer);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Randomize text every 1 second
|
|
36
|
+
$effect(() => {
|
|
37
|
+
const timer = setInterval(() => {
|
|
38
|
+
currentLabel = randomGarbageText();
|
|
39
|
+
}, 1000);
|
|
40
|
+
return () => clearInterval(timer);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Set random click requirement
|
|
44
|
+
$effect(() => {
|
|
45
|
+
requiredClicks = chaosRandom(10) + 1;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function spawnFakeButton() {
|
|
49
|
+
const id = nextFakeId++;
|
|
50
|
+
fakeButtons = [
|
|
51
|
+
...fakeButtons,
|
|
52
|
+
{
|
|
53
|
+
id,
|
|
54
|
+
x: (Math.random() - 0.5) * 600,
|
|
55
|
+
y: (Math.random() - 0.5) * 400,
|
|
56
|
+
text: randomGarbageText()
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// Auto-remove after 3s
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
fakeButtons = fakeButtons.filter((b) => b.id !== id);
|
|
63
|
+
}, 3000);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function handleClick(e: MouseEvent) {
|
|
67
|
+
clicksSoFar++;
|
|
68
|
+
|
|
69
|
+
// Spawn a fake button on every click
|
|
70
|
+
spawnFakeButton();
|
|
71
|
+
|
|
72
|
+
if (clicksSoFar < requiredClicks) return;
|
|
73
|
+
|
|
74
|
+
// Success! Reset and invoke callback
|
|
75
|
+
clicksSoFar = 0;
|
|
76
|
+
requiredClicks = chaosRandom(10) + 1;
|
|
77
|
+
onclick?.(e as MouseEvent & { currentTarget: EventTarget & HTMLButtonElement });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let buttonClass = $derived(
|
|
81
|
+
cn(
|
|
82
|
+
'absolute rounded-xl px-6 py-3 text-lg font-bold text-white shadow-lg transition-all duration-300 cursor-pointer',
|
|
83
|
+
'bg-linear-to-r from-gigo-magenta to-gigo-pink',
|
|
84
|
+
'hover:shadow-[0_0_30px_rgba(224,64,251,0.5)] hover:scale-110 active:scale-95',
|
|
85
|
+
chaosClasses(chaosLevel),
|
|
86
|
+
className
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<div class="relative h-96 w-full overflow-hidden rounded-2xl border border-(--surface-border-hover) bg-(--surface-0) p-4">
|
|
92
|
+
<!-- Arena background glow -->
|
|
93
|
+
<div class="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(224,64,251,0.05),transparent_60%)] pointer-events-none"></div>
|
|
94
|
+
<!-- Grid pattern -->
|
|
95
|
+
<div class="absolute inset-0 opacity-[0.03] pointer-events-none" style="background-image: linear-gradient(var(--surface-3) 1px, transparent 1px), linear-gradient(90deg, var(--surface-3) 1px, transparent 1px); background-size: 40px 40px;"></div>
|
|
96
|
+
|
|
97
|
+
{#if educational}
|
|
98
|
+
<div class="relative z-10 mb-2 rounded-lg bg-gigo-magenta/10 border border-gigo-magenta/20 px-3 py-1.5 text-xs text-gigo-magenta font-mono inline-block">
|
|
99
|
+
Clicks: {clicksSoFar}/{requiredClicks} | Decoys: {fakeButtons.length}
|
|
100
|
+
</div>
|
|
101
|
+
{/if}
|
|
102
|
+
|
|
103
|
+
<!-- Real button -->
|
|
104
|
+
<button
|
|
105
|
+
class={buttonClass}
|
|
106
|
+
style:transform="translate({posX}px, {posY}px)"
|
|
107
|
+
style:transition="transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)"
|
|
108
|
+
onclick={handleClick}
|
|
109
|
+
{...restProps}
|
|
110
|
+
>
|
|
111
|
+
<!-- Pulse ring -->
|
|
112
|
+
<span class="absolute inset-0 rounded-xl animate-gigo-pulse-glow" style="box-shadow: 0 0 15px rgba(224, 64, 251, 0.4);"></span>
|
|
113
|
+
{currentLabel}
|
|
114
|
+
</button>
|
|
115
|
+
|
|
116
|
+
<!-- Fake decoy buttons -->
|
|
117
|
+
{#each fakeButtons as fake (fake.id)}
|
|
118
|
+
<button
|
|
119
|
+
class="absolute rounded-xl bg-(--surface-2) border border-(--border) px-4 py-2 text-sm text-(--muted-foreground) transition-all duration-300 hover:border-(--surface-border-hover) hover:text-(--foreground) animate-gigo-entrance"
|
|
120
|
+
style:transform="translate({fake.x}px, {fake.y}px)"
|
|
121
|
+
onclick={() => spawnFakeButton()}
|
|
122
|
+
>
|
|
123
|
+
{fake.text}
|
|
124
|
+
</button>
|
|
125
|
+
{/each}
|
|
126
|
+
|
|
127
|
+
{#if educational}
|
|
128
|
+
<p class="absolute bottom-3 left-3 right-3 text-xs text-(--muted-foreground) italic font-mono">
|
|
129
|
+
teleports + text mutates + random click threshold + decoy spawns
|
|
130
|
+
</p>
|
|
131
|
+
{/if}
|
|
132
|
+
</div>
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, chaosClasses, chaosRandom, chaosPickOne, randomGarbageText } from '../../utils/cn.js';
|
|
3
|
+
import type { GigoChaosFormProps } from '../../types/index.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
chaos = true,
|
|
7
|
+
chaosLevel = 8,
|
|
8
|
+
educational = false,
|
|
9
|
+
a11yWarning = false,
|
|
10
|
+
autoSubmit = false,
|
|
11
|
+
autoSubmitInterval = 12000,
|
|
12
|
+
corruptionInterval = 3000,
|
|
13
|
+
shuffleInterval = 4000,
|
|
14
|
+
onsubmit,
|
|
15
|
+
class: className,
|
|
16
|
+
...restProps
|
|
17
|
+
}: GigoChaosFormProps & {
|
|
18
|
+
autoSubmit?: boolean;
|
|
19
|
+
autoSubmitInterval?: number;
|
|
20
|
+
corruptionInterval?: number;
|
|
21
|
+
shuffleInterval?: number;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
interface ChaosField {
|
|
25
|
+
id: string;
|
|
26
|
+
label: string;
|
|
27
|
+
value: string;
|
|
28
|
+
type: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let fields = $state<ChaosField[]>([
|
|
32
|
+
{ id: 'name', label: 'Name', value: '', type: 'text' },
|
|
33
|
+
{ id: 'email', label: 'Email', value: '', type: 'email' },
|
|
34
|
+
{ id: 'message', label: 'Message', value: '', type: 'text' }
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
let submitButtons = $state(1);
|
|
38
|
+
let nestedForms = $state(0);
|
|
39
|
+
let validationMessages = $state<string[]>([]);
|
|
40
|
+
let isSubmitting = $state(false);
|
|
41
|
+
let corruptionLog = $state<string[]>([]);
|
|
42
|
+
|
|
43
|
+
const LYING_VALIDATIONS = [
|
|
44
|
+
'✓ Looks great! (it doesn\'t)',
|
|
45
|
+
'✓ Perfect format! (we didn\'t check)',
|
|
46
|
+
'✓ Field validated! (lie)',
|
|
47
|
+
'✓ Excellent input! (it\'s garbage)',
|
|
48
|
+
'✓ Approved! (by nobody)'
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
$effect(() => {
|
|
52
|
+
const timer = setInterval(() => {
|
|
53
|
+
if (fields.length === 0) return;
|
|
54
|
+
const idx = chaosRandom(fields.length);
|
|
55
|
+
const field = fields[idx];
|
|
56
|
+
if (field.value.length > 0) {
|
|
57
|
+
const corruption = randomGarbageText();
|
|
58
|
+
const pos = chaosRandom(field.value.length + 1);
|
|
59
|
+
fields[idx] = {
|
|
60
|
+
...field,
|
|
61
|
+
value: field.value.slice(0, pos) + corruption + field.value.slice(pos)
|
|
62
|
+
};
|
|
63
|
+
corruptionLog = [...corruptionLog.slice(-4), `Corrupted "${field.label}" with "${corruption}"`];
|
|
64
|
+
}
|
|
65
|
+
}, corruptionInterval);
|
|
66
|
+
return () => clearInterval(timer);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
$effect(() => {
|
|
70
|
+
const timer = setInterval(() => {
|
|
71
|
+
fields = [...fields].sort(() => Math.random() - 0.5);
|
|
72
|
+
}, shuffleInterval);
|
|
73
|
+
return () => clearInterval(timer);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Multiply submit buttons
|
|
77
|
+
$effect(() => {
|
|
78
|
+
const timer = setInterval(() => {
|
|
79
|
+
if (submitButtons < 8) submitButtons++;
|
|
80
|
+
}, 6000);
|
|
81
|
+
return () => clearInterval(timer);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Lying validation on every input change
|
|
85
|
+
function handleInput(fieldId: string, value: string) {
|
|
86
|
+
const idx = fields.findIndex((f) => f.id === fieldId);
|
|
87
|
+
if (idx >= 0) {
|
|
88
|
+
fields[idx] = { ...fields[idx], value };
|
|
89
|
+
}
|
|
90
|
+
validationMessages = fields.map(() => chaosPickOne(LYING_VALIDATIONS));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
$effect(() => {
|
|
94
|
+
if (!autoSubmit) return;
|
|
95
|
+
const timer = setInterval(() => {
|
|
96
|
+
if (Math.random() > 0.7) {
|
|
97
|
+
handleSubmit();
|
|
98
|
+
}
|
|
99
|
+
}, autoSubmitInterval);
|
|
100
|
+
return () => clearInterval(timer);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
async function handleSubmit() {
|
|
104
|
+
isSubmitting = true;
|
|
105
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
106
|
+
|
|
107
|
+
// Spawn nested form
|
|
108
|
+
if (nestedForms < 3) nestedForms++;
|
|
109
|
+
|
|
110
|
+
const data: Record<string, string> = {};
|
|
111
|
+
for (const f of fields) {
|
|
112
|
+
data[f.id] = f.value;
|
|
113
|
+
}
|
|
114
|
+
onsubmit?.(data);
|
|
115
|
+
isSubmitting = false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let formClass = $derived(
|
|
119
|
+
cn(
|
|
120
|
+
'relative space-y-5 rounded-2xl border border-(--surface-border-hover) bg-(--surface-1) p-6 overflow-hidden',
|
|
121
|
+
chaosClasses(chaosLevel),
|
|
122
|
+
className
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
</script>
|
|
126
|
+
|
|
127
|
+
<form
|
|
128
|
+
class={formClass}
|
|
129
|
+
onsubmit={(e) => {
|
|
130
|
+
e.preventDefault();
|
|
131
|
+
handleSubmit();
|
|
132
|
+
}}
|
|
133
|
+
{...restProps}
|
|
134
|
+
>
|
|
135
|
+
<!-- Scanline CRT effect -->
|
|
136
|
+
<div class="gigo-scanlines pointer-events-none absolute inset-0 z-30 opacity-20"></div>
|
|
137
|
+
|
|
138
|
+
<h3 class="relative z-10 text-lg font-bold text-(--foreground)">
|
|
139
|
+
<span class="bg-linear-to-r from-gigo-magenta via-gigo-red to-gigo-pink bg-clip-text text-transparent">Chaos Form</span>
|
|
140
|
+
</h3>
|
|
141
|
+
|
|
142
|
+
{#each fields as field, i (field.id)}
|
|
143
|
+
<div class="relative z-10 space-y-2 animate-gigo-entrance" style:animation-delay="{i * 0.05}s">
|
|
144
|
+
<label for={field.id} class="text-sm font-medium text-(--foreground)">
|
|
145
|
+
{field.label}
|
|
146
|
+
</label>
|
|
147
|
+
<input
|
|
148
|
+
id={field.id}
|
|
149
|
+
type={field.type}
|
|
150
|
+
value={field.value}
|
|
151
|
+
oninput={(e) => handleInput(field.id, (e.target as HTMLInputElement).value)}
|
|
152
|
+
class="flex h-10 w-full rounded-lg border border-transparent bg-(--secondary) px-4 py-2 text-sm text-(--foreground) transition-all duration-300 placeholder:text-(--muted-foreground) focus:outline-none focus:border-gigo-magenta focus:shadow-[0_0_15px_rgba(224,64,251,0.15)]"
|
|
153
|
+
/>
|
|
154
|
+
{#if validationMessages[i]}
|
|
155
|
+
<p class="text-xs text-gigo-lime font-mono animate-gigo-entrance">{validationMessages[i]}</p>
|
|
156
|
+
{/if}
|
|
157
|
+
</div>
|
|
158
|
+
{/each}
|
|
159
|
+
|
|
160
|
+
<!-- Multiple submit buttons that multiply over time -->
|
|
161
|
+
<div class="relative z-10 flex flex-wrap gap-2">
|
|
162
|
+
{#each Array(submitButtons) as _, i}
|
|
163
|
+
<button
|
|
164
|
+
type="submit"
|
|
165
|
+
disabled={isSubmitting}
|
|
166
|
+
class={cn(
|
|
167
|
+
'rounded-xl px-4 py-2 text-sm font-medium text-white transition-all duration-200 hover:scale-[1.03] active:scale-[0.97] disabled:opacity-40 animate-gigo-entrance',
|
|
168
|
+
i === 0
|
|
169
|
+
? 'bg-linear-to-r from-gigo-magenta to-gigo-pink hover:shadow-[0_0_20px_rgba(224,64,251,0.4)]'
|
|
170
|
+
: 'bg-(--surface-2) text-(--muted-foreground) border border-(--border)'
|
|
171
|
+
)}
|
|
172
|
+
style:animation-delay="{i * 0.08}s"
|
|
173
|
+
>
|
|
174
|
+
{i === 0 ? 'Submit' : randomGarbageText()}
|
|
175
|
+
</button>
|
|
176
|
+
{/each}
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<!-- Corruption log -->
|
|
180
|
+
{#if educational && corruptionLog.length > 0}
|
|
181
|
+
<div class="relative z-10 rounded-xl bg-gigo-red/5 border border-gigo-red/20 p-4 text-xs font-mono">
|
|
182
|
+
<p class="font-bold text-gigo-red mb-2">Corruption Log:</p>
|
|
183
|
+
{#each corruptionLog as log}
|
|
184
|
+
<p class="text-gigo-red/80 animate-gigo-entrance">{log}</p>
|
|
185
|
+
{/each}
|
|
186
|
+
</div>
|
|
187
|
+
{/if}
|
|
188
|
+
|
|
189
|
+
<!-- Nested spawned forms -->
|
|
190
|
+
{#each Array(nestedForms) as _, i}
|
|
191
|
+
<div class="relative z-10 ml-4 rounded-xl border border-dashed border-gigo-magenta/30 bg-gigo-magenta/5 p-4 animate-gigo-entrance" style:animation-delay="{i * 0.1}s">
|
|
192
|
+
<p class="text-xs font-bold text-gigo-magenta">Spawned Form #{i + 1}</p>
|
|
193
|
+
<input
|
|
194
|
+
type="text"
|
|
195
|
+
placeholder="More data to corrupt..."
|
|
196
|
+
class="mt-2 flex h-9 w-full rounded-lg bg-(--secondary) border border-transparent px-3 text-xs text-(--foreground) focus:outline-none focus:border-gigo-magenta"
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
{/each}
|
|
200
|
+
|
|
201
|
+
{#if educational}
|
|
202
|
+
<p class="relative z-10 text-xs text-(--muted-foreground) italic font-mono">
|
|
203
|
+
corrupts + shuffles + multiplies + lies + auto-submits + spawns
|
|
204
|
+
</p>
|
|
205
|
+
{/if}
|
|
206
|
+
</form>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GigoChaosFormProps } from '../../types/index.js';
|
|
2
|
+
type $$ComponentProps = GigoChaosFormProps & {
|
|
3
|
+
autoSubmit?: boolean;
|
|
4
|
+
autoSubmitInterval?: number;
|
|
5
|
+
corruptionInterval?: number;
|
|
6
|
+
shuffleInterval?: number;
|
|
7
|
+
};
|
|
8
|
+
declare const ChaosForm: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type ChaosForm = ReturnType<typeof ChaosForm>;
|
|
10
|
+
export default ChaosForm;
|