@gigo-ui/components 1.0.0-alpha
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 +49 -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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../utils/cn.js';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
value = $bindable('#e040fb'),
|
|
6
|
+
offsetHue = true,
|
|
7
|
+
swapChannels = true,
|
|
8
|
+
delayUpdate = true,
|
|
9
|
+
class: className,
|
|
10
|
+
...restProps
|
|
11
|
+
}: {
|
|
12
|
+
value?: string;
|
|
13
|
+
offsetHue?: boolean;
|
|
14
|
+
swapChannels?: boolean;
|
|
15
|
+
delayUpdate?: boolean;
|
|
16
|
+
class?: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
} = $props();
|
|
19
|
+
|
|
20
|
+
let pickerValue = $state('#e040fb');
|
|
21
|
+
let displayColor = $state('#e040fb');
|
|
22
|
+
let actualColor = $state('#e040fb');
|
|
23
|
+
let labelLie = $state('');
|
|
24
|
+
|
|
25
|
+
const COLOR_NAMES: Record<string, string> = {
|
|
26
|
+
red: 'Definitely Blue', blue: 'Clearly Red', green: 'Obviously Purple',
|
|
27
|
+
yellow: 'Totally Black', black: 'Pure White', white: 'Deep Black',
|
|
28
|
+
pink: 'Forest Green', orange: 'Ocean Blue', purple: 'Grass Green',
|
|
29
|
+
cyan: 'Warm Orange', magenta: 'Cool Teal', lime: 'Hot Pink',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function hexToRgb(hex: string): [number, number, number] {
|
|
33
|
+
const h = hex.replace('#', '');
|
|
34
|
+
return [
|
|
35
|
+
parseInt(h.substring(0, 2), 16),
|
|
36
|
+
parseInt(h.substring(2, 4), 16),
|
|
37
|
+
parseInt(h.substring(4, 6), 16),
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function rgbToHex(r: number, g: number, b: number): string {
|
|
42
|
+
return '#' + [r, g, b].map(c => Math.max(0, Math.min(255, Math.round(c))).toString(16).padStart(2, '0')).join('');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function corruptColor(hex: string): string {
|
|
46
|
+
let [r, g, b] = hexToRgb(hex);
|
|
47
|
+
|
|
48
|
+
if (swapChannels) {
|
|
49
|
+
[r, g, b] = [b, r, g];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (offsetHue) {
|
|
53
|
+
r = (r + 128) % 256;
|
|
54
|
+
g = (g + 64) % 256;
|
|
55
|
+
b = (b + 192) % 256;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return rgbToHex(r, g, b);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getClosestColorName(hex: string): string {
|
|
62
|
+
const [r, g, b] = hexToRgb(hex);
|
|
63
|
+
const names = Object.keys(COLOR_NAMES);
|
|
64
|
+
const hue = Math.atan2(Math.sqrt(3) * (g - b), 2 * r - g - b) * 180 / Math.PI;
|
|
65
|
+
const idx = Math.abs(Math.round(hue / 30)) % names.length;
|
|
66
|
+
return COLOR_NAMES[names[idx]] || 'Unknown Color';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handlePick(e: Event) {
|
|
70
|
+
const raw = (e.target as HTMLInputElement).value;
|
|
71
|
+
pickerValue = raw;
|
|
72
|
+
|
|
73
|
+
const corrupted = corruptColor(raw);
|
|
74
|
+
|
|
75
|
+
if (delayUpdate) {
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
displayColor = corrupted;
|
|
78
|
+
actualColor = raw;
|
|
79
|
+
value = corrupted;
|
|
80
|
+
labelLie = getClosestColorName(raw);
|
|
81
|
+
}, 300 + Math.random() * 500);
|
|
82
|
+
} else {
|
|
83
|
+
displayColor = corrupted;
|
|
84
|
+
actualColor = raw;
|
|
85
|
+
value = corrupted;
|
|
86
|
+
labelLie = getClosestColorName(raw);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<div class={cn('space-y-4 max-w-xs', className)} {...restProps}>
|
|
92
|
+
<div class="flex items-center justify-between">
|
|
93
|
+
<span class="text-xs font-mono text-(--muted-foreground)">Pick a color</span>
|
|
94
|
+
{#if labelLie}
|
|
95
|
+
<span class="text-xs font-mono text-gigo-cyan">{labelLie}</span>
|
|
96
|
+
{/if}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div class="flex items-center gap-4">
|
|
100
|
+
<input
|
|
101
|
+
type="color"
|
|
102
|
+
value={pickerValue}
|
|
103
|
+
oninput={handlePick}
|
|
104
|
+
class="w-16 h-16 rounded-xl border border-(--border) cursor-pointer bg-transparent
|
|
105
|
+
[&::-webkit-color-swatch-wrapper]:p-1 [&::-webkit-color-swatch]:rounded-lg [&::-webkit-color-swatch]:border-none
|
|
106
|
+
[&::-moz-color-swatch]:rounded-lg [&::-moz-color-swatch]:border-none"
|
|
107
|
+
/>
|
|
108
|
+
|
|
109
|
+
<div class="flex-1 space-y-2">
|
|
110
|
+
<div class="flex items-center gap-2">
|
|
111
|
+
<div class="w-8 h-8 rounded-lg border border-(--border)" style:background={actualColor}></div>
|
|
112
|
+
<span class="text-[10px] font-mono text-(--muted-foreground)">You picked</span>
|
|
113
|
+
<span class="font-mono text-xs">{actualColor}</span>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="flex items-center gap-2">
|
|
116
|
+
<div class="w-8 h-8 rounded-lg border border-(--border)" style:background={displayColor}></div>
|
|
117
|
+
<span class="text-[10px] font-mono text-(--muted-foreground)">You get</span>
|
|
118
|
+
<span class="font-mono text-xs text-gigo-magenta">{displayColor}</span>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div
|
|
124
|
+
class="h-20 w-full rounded-xl border border-(--border) transition-colors duration-500"
|
|
125
|
+
style:background={displayColor}
|
|
126
|
+
>
|
|
127
|
+
<div class="flex h-full items-center justify-center">
|
|
128
|
+
<span
|
|
129
|
+
class="text-sm font-mono font-bold px-3 py-1 rounded-lg"
|
|
130
|
+
style:background="rgba(0,0,0,0.5)"
|
|
131
|
+
style:color="white"
|
|
132
|
+
>
|
|
133
|
+
{displayColor}
|
|
134
|
+
</span>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<p class="text-[10px] font-mono text-(--muted-foreground) text-center">
|
|
139
|
+
Every color you pick gets channel-swapped and hue-shifted. Good luck matching anything.
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
value?: string;
|
|
3
|
+
offsetHue?: boolean;
|
|
4
|
+
swapChannels?: boolean;
|
|
5
|
+
delayUpdate?: boolean;
|
|
6
|
+
class?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
declare const ColorPickerWrong: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
10
|
+
type ColorPickerWrong = ReturnType<typeof ColorPickerWrong>;
|
|
11
|
+
export default ColorPickerWrong;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../utils/cn.js';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
value = $bindable(''),
|
|
6
|
+
class: className,
|
|
7
|
+
...restProps
|
|
8
|
+
}: {
|
|
9
|
+
value?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
let expression = $state<string[]>([]);
|
|
15
|
+
let result = $state<string>('');
|
|
16
|
+
let showResult = $state(false);
|
|
17
|
+
|
|
18
|
+
const DIGIT_OPTIONS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
19
|
+
const OPERATOR_OPTIONS = ['+', '-', '×', '÷'];
|
|
20
|
+
const DECIMAL_OPTIONS = ['.'];
|
|
21
|
+
|
|
22
|
+
let digitDropdowns = $state<(string | null)[]>([null, null, null, null, null, null, null, null]);
|
|
23
|
+
let operatorDropdown = $state<string | null>(null);
|
|
24
|
+
|
|
25
|
+
function addDigit(index: number, val: string) {
|
|
26
|
+
digitDropdowns = digitDropdowns.map((d, i) => i === index ? val : d);
|
|
27
|
+
rebuildExpression();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function setOperator(val: string) {
|
|
31
|
+
operatorDropdown = val;
|
|
32
|
+
rebuildExpression();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function rebuildExpression() {
|
|
36
|
+
const parts: string[] = [];
|
|
37
|
+
// First number
|
|
38
|
+
const num1Digits = digitDropdowns.slice(0, 4).filter(d => d !== null);
|
|
39
|
+
if (num1Digits.length) parts.push(num1Digits.join(''));
|
|
40
|
+
|
|
41
|
+
// Operator
|
|
42
|
+
if (operatorDropdown) parts.push(operatorDropdown);
|
|
43
|
+
|
|
44
|
+
// Second number
|
|
45
|
+
const num2Digits = digitDropdowns.slice(4, 8).filter(d => d !== null);
|
|
46
|
+
if (num2Digits.length) parts.push(num2Digits.join(''));
|
|
47
|
+
|
|
48
|
+
expression = parts;
|
|
49
|
+
value = parts.join(' ');
|
|
50
|
+
showResult = false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function calculate() {
|
|
54
|
+
const num1Str = digitDropdowns.slice(0, 4).filter(d => d !== null).join('');
|
|
55
|
+
const num2Str = digitDropdowns.slice(4, 8).filter(d => d !== null).join('');
|
|
56
|
+
const num1 = parseFloat(num1Str);
|
|
57
|
+
const num2 = parseFloat(num2Str);
|
|
58
|
+
|
|
59
|
+
if (isNaN(num1) || isNaN(num2) || !operatorDropdown) {
|
|
60
|
+
result = 'ERR: Incomplete';
|
|
61
|
+
showResult = true;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let res: number;
|
|
66
|
+
switch (operatorDropdown) {
|
|
67
|
+
case '+': res = num1 + num2; break;
|
|
68
|
+
case '-': res = num1 - num2; break;
|
|
69
|
+
case '×': res = num1 * num2; break;
|
|
70
|
+
case '÷': res = num2 === 0 ? NaN : num1 / num2; break;
|
|
71
|
+
default: res = NaN;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
result = isNaN(res) ? 'ERR: ÷ by 0' : String(Math.round(res * 10000) / 10000);
|
|
75
|
+
showResult = true;
|
|
76
|
+
value = `${num1} ${operatorDropdown} ${num2} = ${result}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function clearAll() {
|
|
80
|
+
digitDropdowns = [null, null, null, null, null, null, null, null];
|
|
81
|
+
operatorDropdown = null;
|
|
82
|
+
expression = [];
|
|
83
|
+
result = '';
|
|
84
|
+
showResult = false;
|
|
85
|
+
value = '';
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<div class={cn('space-y-4 max-w-lg', className)} {...restProps}>
|
|
90
|
+
<!-- Display -->
|
|
91
|
+
<div class="rounded-xl border border-(--border) bg-(--secondary) px-4 py-3">
|
|
92
|
+
<div class="font-mono text-right text-sm text-(--muted-foreground) min-h-[1.2em]">
|
|
93
|
+
{expression.join(' ') || '_ ? _ = ?'}
|
|
94
|
+
</div>
|
|
95
|
+
{#if showResult}
|
|
96
|
+
<div class="font-mono text-right text-2xl font-bold mt-1"
|
|
97
|
+
style:color={result.startsWith('ERR') ? '#ff1744' : '#76ff03'}>
|
|
98
|
+
{result}
|
|
99
|
+
</div>
|
|
100
|
+
{/if}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<!-- Dropdown rows -->
|
|
104
|
+
<div class="space-y-3">
|
|
105
|
+
<!-- First number label -->
|
|
106
|
+
<div class="text-[10px] font-mono text-(--muted-foreground) uppercase tracking-wider">First Number (select each digit)</div>
|
|
107
|
+
|
|
108
|
+
<div class="flex gap-2 flex-wrap">
|
|
109
|
+
{#each [0, 1, 2, 3] as i}
|
|
110
|
+
<div class="flex flex-col items-center gap-1">
|
|
111
|
+
<span class="text-[9px] font-mono text-(--muted-foreground)">
|
|
112
|
+
{i === 0 ? 'Thou.' : i === 1 ? 'Hund.' : i === 2 ? 'Tens' : 'Ones'}
|
|
113
|
+
</span>
|
|
114
|
+
<select
|
|
115
|
+
class="w-16 h-9 rounded-lg border border-(--border) bg-(--card) text-center font-mono text-sm
|
|
116
|
+
text-(--foreground) cursor-pointer appearance-none
|
|
117
|
+
focus:outline-none focus:border-gigo-magenta focus:ring-1 focus:ring-gigo-magenta/30"
|
|
118
|
+
value={digitDropdowns[i] ?? ''}
|
|
119
|
+
onchange={(e) => addDigit(i, (e.target as HTMLSelectElement).value)}
|
|
120
|
+
>
|
|
121
|
+
<option value="" disabled selected>—</option>
|
|
122
|
+
{#each DIGIT_OPTIONS as d}
|
|
123
|
+
<option value={d}>{d}</option>
|
|
124
|
+
{/each}
|
|
125
|
+
</select>
|
|
126
|
+
</div>
|
|
127
|
+
{/each}
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- Operator -->
|
|
131
|
+
<div class="text-[10px] font-mono text-(--muted-foreground) uppercase tracking-wider">Operator</div>
|
|
132
|
+
|
|
133
|
+
<select
|
|
134
|
+
class="w-20 h-9 rounded-lg border border-(--border) bg-(--card) text-center font-mono text-lg
|
|
135
|
+
text-gigo-magenta cursor-pointer appearance-none
|
|
136
|
+
focus:outline-none focus:border-gigo-magenta focus:ring-1 focus:ring-gigo-magenta/30"
|
|
137
|
+
value={operatorDropdown ?? ''}
|
|
138
|
+
onchange={(e) => setOperator((e.target as HTMLSelectElement).value)}
|
|
139
|
+
>
|
|
140
|
+
<option value="" disabled selected>?</option>
|
|
141
|
+
{#each OPERATOR_OPTIONS as op}
|
|
142
|
+
<option value={op}>{op}</option>
|
|
143
|
+
{/each}
|
|
144
|
+
</select>
|
|
145
|
+
|
|
146
|
+
<!-- Second number -->
|
|
147
|
+
<div class="text-[10px] font-mono text-(--muted-foreground) uppercase tracking-wider">Second Number (select each digit)</div>
|
|
148
|
+
|
|
149
|
+
<div class="flex gap-2 flex-wrap">
|
|
150
|
+
{#each [4, 5, 6, 7] as i}
|
|
151
|
+
<div class="flex flex-col items-center gap-1">
|
|
152
|
+
<span class="text-[9px] font-mono text-(--muted-foreground)">
|
|
153
|
+
{i === 4 ? 'Thou.' : i === 5 ? 'Hund.' : i === 6 ? 'Tens' : 'Ones'}
|
|
154
|
+
</span>
|
|
155
|
+
<select
|
|
156
|
+
class="w-16 h-9 rounded-lg border border-(--border) bg-(--card) text-center font-mono text-sm
|
|
157
|
+
text-(--foreground) cursor-pointer appearance-none
|
|
158
|
+
focus:outline-none focus:border-gigo-magenta focus:ring-1 focus:ring-gigo-magenta/30"
|
|
159
|
+
value={digitDropdowns[i] ?? ''}
|
|
160
|
+
onchange={(e) => addDigit(i, (e.target as HTMLSelectElement).value)}
|
|
161
|
+
>
|
|
162
|
+
<option value="" disabled selected>—</option>
|
|
163
|
+
{#each DIGIT_OPTIONS as d}
|
|
164
|
+
<option value={d}>{d}</option>
|
|
165
|
+
{/each}
|
|
166
|
+
</select>
|
|
167
|
+
</div>
|
|
168
|
+
{/each}
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<!-- Action buttons -->
|
|
173
|
+
<div class="flex gap-3">
|
|
174
|
+
<button
|
|
175
|
+
class="flex-1 px-4 py-2.5 rounded-xl font-mono text-sm font-bold
|
|
176
|
+
bg-gigo-magenta/15 border border-gigo-magenta/50 text-gigo-magenta
|
|
177
|
+
hover:bg-gigo-magenta/25 transition-colors cursor-pointer"
|
|
178
|
+
onclick={calculate}
|
|
179
|
+
>
|
|
180
|
+
= Calculate
|
|
181
|
+
</button>
|
|
182
|
+
<button
|
|
183
|
+
class="px-4 py-2.5 rounded-xl font-mono text-sm
|
|
184
|
+
bg-(--secondary) border border-(--border) text-(--muted-foreground)
|
|
185
|
+
hover:text-gigo-red hover:border-gigo-red/30 transition-colors cursor-pointer"
|
|
186
|
+
onclick={clearAll}
|
|
187
|
+
>
|
|
188
|
+
Clear
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<p class="text-[10px] font-mono text-(--muted-foreground) text-center">
|
|
193
|
+
Every. Single. Digit. Is. A. Dropdown. You're welcome.
|
|
194
|
+
</p>
|
|
195
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
value?: string;
|
|
3
|
+
class?: string;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
};
|
|
6
|
+
declare const DropdownCalc: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
7
|
+
type DropdownCalc = ReturnType<typeof DropdownCalc>;
|
|
8
|
+
export default DropdownCalc;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../utils/cn.js';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
title = 'Ghost Card',
|
|
6
|
+
description = '',
|
|
7
|
+
children,
|
|
8
|
+
disappearMinMs = 2000,
|
|
9
|
+
disappearMaxMs = 5000,
|
|
10
|
+
glitchChance = 0.6,
|
|
11
|
+
class: className,
|
|
12
|
+
...restProps
|
|
13
|
+
}: {
|
|
14
|
+
title?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
children?: import('svelte').Snippet;
|
|
17
|
+
disappearMinMs?: number;
|
|
18
|
+
disappearMaxMs?: number;
|
|
19
|
+
glitchChance?: number;
|
|
20
|
+
class?: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
let visible = $state(true);
|
|
25
|
+
let glitching = $state(false);
|
|
26
|
+
let offsetX = $state(0);
|
|
27
|
+
let offsetY = $state(0);
|
|
28
|
+
let rotation = $state(0);
|
|
29
|
+
let scaleX = $state(1);
|
|
30
|
+
let clipPath = $state('none');
|
|
31
|
+
let hueShift = $state(0);
|
|
32
|
+
let containerEl: HTMLDivElement | undefined = $state();
|
|
33
|
+
|
|
34
|
+
let glitchContentOpacity = $state(1);
|
|
35
|
+
let glitchTitleStrike = $state(false);
|
|
36
|
+
let glitchTitleText = $state('');
|
|
37
|
+
let glitchScanlineTop = $state(50);
|
|
38
|
+
|
|
39
|
+
const GLITCH_CLIPS = [
|
|
40
|
+
'polygon(0 0, 100% 0, 100% 45%, 0 45%)',
|
|
41
|
+
'polygon(0 55%, 100% 55%, 100% 100%, 0 100%)',
|
|
42
|
+
'polygon(0 0, 60% 0, 60% 100%, 0 100%)',
|
|
43
|
+
'polygon(20% 10%, 80% 10%, 80% 90%, 20% 90%)',
|
|
44
|
+
'inset(10% 0 10% 0)',
|
|
45
|
+
'polygon(0 0, 100% 0, 100% 30%, 70% 30%, 70% 70%, 100% 70%, 100% 100%, 0 100%)',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
function corruptTitle(text: string): string {
|
|
49
|
+
return text.split('').map(c =>
|
|
50
|
+
Math.random() > 0.7 ? String.fromCharCode(c.charCodeAt(0) + Math.floor(Math.random() * 5)) : c
|
|
51
|
+
).join('');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
$effect(() => {
|
|
55
|
+
const disappear = () => {
|
|
56
|
+
const nextDisappear = disappearMinMs + Math.random() * (disappearMaxMs - disappearMinMs);
|
|
57
|
+
|
|
58
|
+
const timer = setTimeout(() => {
|
|
59
|
+
visible = false;
|
|
60
|
+
|
|
61
|
+
const reappearDelay = 500 + Math.random() * 2000;
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
offsetX = (Math.random() - 0.5) * 60;
|
|
64
|
+
offsetY = (Math.random() - 0.5) * 40;
|
|
65
|
+
rotation = (Math.random() - 0.5) * 12;
|
|
66
|
+
|
|
67
|
+
if (Math.random() < glitchChance) {
|
|
68
|
+
glitching = true;
|
|
69
|
+
scaleX = Math.random() > 0.5 ? -1 : 1;
|
|
70
|
+
clipPath = Math.random() > 0.3 ? GLITCH_CLIPS[Math.floor(Math.random() * GLITCH_CLIPS.length)] : 'none';
|
|
71
|
+
hueShift = Math.random() * 360;
|
|
72
|
+
glitchContentOpacity = Math.random() > 0.5 ? 0.6 : 1;
|
|
73
|
+
glitchTitleStrike = Math.random() > 0.5;
|
|
74
|
+
glitchTitleText = Math.random() > 0.6 ? corruptTitle(title) : '';
|
|
75
|
+
glitchScanlineTop = Math.random() * 100;
|
|
76
|
+
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
glitching = false;
|
|
79
|
+
scaleX = 1;
|
|
80
|
+
clipPath = 'none';
|
|
81
|
+
hueShift = 0;
|
|
82
|
+
glitchContentOpacity = 1;
|
|
83
|
+
glitchTitleStrike = false;
|
|
84
|
+
glitchTitleText = '';
|
|
85
|
+
}, 800 + Math.random() * 1500);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
visible = true;
|
|
89
|
+
disappear();
|
|
90
|
+
}, reappearDelay);
|
|
91
|
+
}, nextDisappear);
|
|
92
|
+
|
|
93
|
+
return timer;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const t = disappear();
|
|
97
|
+
return () => clearTimeout(t);
|
|
98
|
+
});
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<div
|
|
102
|
+
class="relative"
|
|
103
|
+
bind:this={containerEl}
|
|
104
|
+
>
|
|
105
|
+
<div
|
|
106
|
+
class={cn(
|
|
107
|
+
'relative rounded-2xl border bg-(--card) p-6 transition-all duration-500',
|
|
108
|
+
visible ? 'opacity-100' : 'opacity-0 scale-75',
|
|
109
|
+
glitching ? 'border-gigo-red/50' : 'border-(--border)',
|
|
110
|
+
className
|
|
111
|
+
)}
|
|
112
|
+
style:transform="translate({offsetX}px, {offsetY}px) rotate({rotation}deg) scaleX({scaleX})"
|
|
113
|
+
style:clip-path={clipPath}
|
|
114
|
+
style:filter="hue-rotate({hueShift}deg)"
|
|
115
|
+
style:transition="opacity 0.3s, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1), clip-path 0.2s, filter 0.3s"
|
|
116
|
+
{...restProps}
|
|
117
|
+
>
|
|
118
|
+
{#if glitching}
|
|
119
|
+
<div class="absolute inset-0 pointer-events-none overflow-hidden rounded-2xl z-10">
|
|
120
|
+
<div class="absolute inset-0 bg-gigo-red/5"></div>
|
|
121
|
+
<div
|
|
122
|
+
class="absolute w-full h-0.5 bg-gigo-cyan/30"
|
|
123
|
+
style:top="{glitchScanlineTop}%"
|
|
124
|
+
style:animation="gigo-shimmer 0.5s linear infinite"
|
|
125
|
+
></div>
|
|
126
|
+
</div>
|
|
127
|
+
{/if}
|
|
128
|
+
|
|
129
|
+
<div class="relative z-0" style:opacity={glitchContentOpacity}>
|
|
130
|
+
{#if title}
|
|
131
|
+
<h3 class="text-lg font-semibold text-(--foreground)" class:line-through={glitchTitleStrike}>
|
|
132
|
+
{glitchTitleText || title}
|
|
133
|
+
</h3>
|
|
134
|
+
{/if}
|
|
135
|
+
{#if description}
|
|
136
|
+
<p class="mt-2 text-sm text-(--muted-foreground)">{description}</p>
|
|
137
|
+
{/if}
|
|
138
|
+
{#if children}
|
|
139
|
+
<div class="mt-4">
|
|
140
|
+
{@render children()}
|
|
141
|
+
</div>
|
|
142
|
+
{/if}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<!-- Ghost status indicator -->
|
|
146
|
+
<div class="absolute top-3 right-3 flex items-center gap-1.5">
|
|
147
|
+
<div
|
|
148
|
+
class="w-2 h-2 rounded-full transition-colors duration-300"
|
|
149
|
+
style:background={glitching ? '#ff1744' : visible ? '#76ff03' : '#7a7a8e'}
|
|
150
|
+
style:box-shadow={glitching ? '0 0 8px rgba(255, 23, 68, 0.6)' : 'none'}
|
|
151
|
+
></div>
|
|
152
|
+
<span class="text-[10px] font-mono text-(--muted-foreground)">
|
|
153
|
+
{glitching ? 'CORRUPT' : visible ? 'STABLE' : 'GONE'}
|
|
154
|
+
</span>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
title?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
children?: import('svelte').Snippet;
|
|
5
|
+
disappearMinMs?: number;
|
|
6
|
+
disappearMaxMs?: number;
|
|
7
|
+
glitchChance?: number;
|
|
8
|
+
class?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
declare const GhostCard: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type GhostCard = ReturnType<typeof GhostCard>;
|
|
13
|
+
export default GhostCard;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../utils/cn.js';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
value = $bindable(''),
|
|
6
|
+
placeholder = 'Catch the digits...',
|
|
7
|
+
spawnInterval = 400,
|
|
8
|
+
maxFalling = 15,
|
|
9
|
+
characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@.!?#$%',
|
|
10
|
+
arenaHeight = 224,
|
|
11
|
+
class: className,
|
|
12
|
+
...restProps
|
|
13
|
+
}: {
|
|
14
|
+
value?: string;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
spawnInterval?: number;
|
|
17
|
+
maxFalling?: number;
|
|
18
|
+
characters?: string;
|
|
19
|
+
arenaHeight?: number;
|
|
20
|
+
class?: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
interface FallingDigit {
|
|
25
|
+
id: number;
|
|
26
|
+
char: string;
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
speed: number;
|
|
30
|
+
caught: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let digits = $state<FallingDigit[]>([]);
|
|
34
|
+
let nextId = $state(0);
|
|
35
|
+
let containerEl: HTMLDivElement | undefined = $state();
|
|
36
|
+
let containerWidth = $state(400);
|
|
37
|
+
let containerHeight = $state(200);
|
|
38
|
+
|
|
39
|
+
$effect(() => {
|
|
40
|
+
const timer = setInterval(() => {
|
|
41
|
+
if (digits.length > maxFalling) return;
|
|
42
|
+
const id = nextId++;
|
|
43
|
+
const w = containerEl?.offsetWidth ?? 400;
|
|
44
|
+
digits = [...digits, {
|
|
45
|
+
id,
|
|
46
|
+
char: characters[Math.floor(Math.random() * characters.length)],
|
|
47
|
+
x: 20 + Math.random() * (w - 60),
|
|
48
|
+
y: -30,
|
|
49
|
+
speed: 0.6 + Math.random() * 1.5,
|
|
50
|
+
caught: false
|
|
51
|
+
}];
|
|
52
|
+
}, spawnInterval);
|
|
53
|
+
return () => clearInterval(timer);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Gravity animation frame
|
|
57
|
+
$effect(() => {
|
|
58
|
+
let running = true;
|
|
59
|
+
const h = containerEl?.offsetHeight ?? 200;
|
|
60
|
+
|
|
61
|
+
function tick() {
|
|
62
|
+
if (!running) return;
|
|
63
|
+
digits = digits
|
|
64
|
+
.map(d => ({
|
|
65
|
+
...d,
|
|
66
|
+
y: d.caught ? d.y : d.y + d.speed
|
|
67
|
+
}))
|
|
68
|
+
.filter(d => d.y < h + 40 || d.caught);
|
|
69
|
+
requestAnimationFrame(tick);
|
|
70
|
+
}
|
|
71
|
+
requestAnimationFrame(tick);
|
|
72
|
+
return () => { running = false; };
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Update container dimensions
|
|
76
|
+
$effect(() => {
|
|
77
|
+
if (!containerEl) return;
|
|
78
|
+
const obs = new ResizeObserver(entries => {
|
|
79
|
+
for (const e of entries) {
|
|
80
|
+
containerWidth = e.contentRect.width;
|
|
81
|
+
containerHeight = e.contentRect.height;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
obs.observe(containerEl);
|
|
85
|
+
return () => obs.disconnect();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function catchDigit(digit: FallingDigit) {
|
|
89
|
+
if (digit.caught) return;
|
|
90
|
+
value += digit.char;
|
|
91
|
+
digits = digits.filter(d => d.id !== digit.id);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function removeLastChar() {
|
|
95
|
+
value = value.slice(0, -1);
|
|
96
|
+
}
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<div class={cn('space-y-3', className)}>
|
|
100
|
+
<!-- Display current value -->
|
|
101
|
+
<div class="flex items-center gap-2 rounded-xl border border-(--border) bg-(--secondary) px-4 py-2.5 min-h-10.5">
|
|
102
|
+
<span class="text-sm text-(--foreground) font-mono flex-1 break-all">
|
|
103
|
+
{#if value}
|
|
104
|
+
{value}
|
|
105
|
+
{:else}
|
|
106
|
+
<span class="text-(--muted-foreground)">{placeholder}</span>
|
|
107
|
+
{/if}
|
|
108
|
+
</span>
|
|
109
|
+
{#if value}
|
|
110
|
+
<button
|
|
111
|
+
class="text-xs text-(--muted-foreground) hover:text-gigo-red transition-colors px-1.5 py-0.5 rounded"
|
|
112
|
+
onclick={removeLastChar}
|
|
113
|
+
>
|
|
114
|
+
⌫
|
|
115
|
+
</button>
|
|
116
|
+
{/if}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- Falling digit arena -->
|
|
120
|
+
<div
|
|
121
|
+
bind:this={containerEl}
|
|
122
|
+
class="relative w-full overflow-hidden rounded-2xl border border-(--border) bg-(--card)"
|
|
123
|
+
style:height="{arenaHeight}px"
|
|
124
|
+
{...restProps}
|
|
125
|
+
>
|
|
126
|
+
<!-- Background grid -->
|
|
127
|
+
<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: 32px 32px;"></div>
|
|
128
|
+
|
|
129
|
+
<!-- Catch zone at bottom -->
|
|
130
|
+
<div class="absolute bottom-0 left-0 right-0 h-10 bg-linear-to-t from-gigo-magenta/10 to-transparent pointer-events-none flex items-end justify-center pb-1">
|
|
131
|
+
<span class="text-[10px] font-mono text-gigo-magenta/50 uppercase tracking-widest">catch zone</span>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<!-- Falling digits -->
|
|
135
|
+
{#each digits as digit (digit.id)}
|
|
136
|
+
<button
|
|
137
|
+
class="absolute flex items-center justify-center w-9 h-9 rounded-lg text-sm font-mono font-bold cursor-pointer transition-all duration-100 hover:scale-125 active:scale-90 select-none"
|
|
138
|
+
style:left="{digit.x}px"
|
|
139
|
+
style:top="{digit.y}px"
|
|
140
|
+
style:background={digit.y > (containerHeight - 60) ? 'rgba(224, 64, 251, 0.2)' : 'rgba(255, 255, 255, 0.05)'}
|
|
141
|
+
style:border="1px solid {digit.y > (containerHeight - 60) ? 'rgba(224, 64, 251, 0.5)' : 'rgba(255, 255, 255, 0.1)'}"
|
|
142
|
+
style:color={digit.y > (containerHeight - 60) ? '#e040fb' : 'var(--foreground)'}
|
|
143
|
+
style:box-shadow={digit.y > (containerHeight - 60) ? '0 0 12px rgba(224, 64, 251, 0.3)' : 'none'}
|
|
144
|
+
onclick={() => catchDigit(digit)}
|
|
145
|
+
>
|
|
146
|
+
{digit.char}
|
|
147
|
+
</button>
|
|
148
|
+
{/each}
|
|
149
|
+
|
|
150
|
+
<!-- Instructions -->
|
|
151
|
+
{#if digits.length === 0 && !value}
|
|
152
|
+
<div class="absolute inset-0 flex items-center justify-center text-sm text-(--muted-foreground)">
|
|
153
|
+
Digits spawning...
|
|
154
|
+
</div>
|
|
155
|
+
{/if}
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<p class="text-[10px] font-mono text-(--muted-foreground) text-center">
|
|
159
|
+
Click falling characters to type. Good luck spelling anything useful.
|
|
160
|
+
</p>
|
|
161
|
+
</div>
|