@oalacea/chaosui 0.1.0 → 0.4.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/bin/cli.js +75 -13
- package/components/backgrounds/glow-orbs/index.tsx +1 -1
- package/components/buttons/chaos-button/chaos-button.module.css +3 -2
- package/components/buttons/glitch-button/glitch-button.module.css +7 -7
- package/components/chaos-vars.css +27 -0
- package/components/cyber/cyber-avatar/css/cyber-avatar.module.css +60 -0
- package/components/cyber/cyber-avatar/css/index.tsx +28 -0
- package/components/cyber/cyber-avatar/tailwind/index.tsx +46 -0
- package/components/cyber/cyber-input/css/cyber-input.module.css +87 -0
- package/components/cyber/cyber-input/css/index.tsx +49 -0
- package/components/cyber/cyber-input/tailwind/index.tsx +55 -0
- package/components/cyber/cyber-loader/css/cyber-loader.module.css +102 -0
- package/components/cyber/cyber-loader/css/index.tsx +58 -0
- package/components/cyber/cyber-loader/tailwind/index.tsx +63 -0
- package/components/cyber/cyber-modal/css/cyber-modal.module.css +124 -0
- package/components/cyber/cyber-modal/css/index.tsx +75 -0
- package/components/cyber/cyber-modal/tailwind/index.tsx +87 -0
- package/components/cyber/cyber-slider/css/cyber-slider.module.css +61 -0
- package/components/cyber/cyber-slider/css/index.tsx +41 -0
- package/components/cyber/cyber-slider/tailwind/index.tsx +51 -0
- package/components/cyber/cyber-tooltip/css/cyber-tooltip.module.css +67 -0
- package/components/cyber/cyber-tooltip/css/index.tsx +36 -0
- package/components/cyber/cyber-tooltip/tailwind/index.tsx +48 -0
- package/components/effects/glitch-image/css/glitch-image.module.css +64 -0
- package/components/effects/glitch-image/css/index.tsx +25 -0
- package/components/effects/glitch-image/tailwind/index.tsx +49 -0
- package/components/effects/glowing-border/css/glowing-border.module.css +73 -0
- package/components/effects/glowing-border/css/index.tsx +45 -0
- package/components/effects/glowing-border/tailwind/index.tsx +40 -0
- package/components/effects/screen-distortion/screen-distortion.module.css +2 -2
- package/components/effects/warning-tape/index.tsx +4 -4
- package/components/effects/warning-tape/warning-tape.module.css +2 -0
- package/components/layout/data-grid/css/data-grid.module.css +52 -0
- package/components/layout/data-grid/css/index.tsx +76 -0
- package/components/layout/data-grid/tailwind/index.tsx +74 -0
- package/components/layout/hologram-card/css/hologram-card.module.css +102 -0
- package/components/layout/hologram-card/css/index.tsx +46 -0
- package/components/layout/hologram-card/tailwind/index.tsx +61 -0
- package/components/navigation/hexagon-menu/css/hexagon-menu.module.css +55 -0
- package/components/navigation/hexagon-menu/css/index.tsx +35 -0
- package/components/navigation/hexagon-menu/tailwind/index.tsx +53 -0
- package/components/neon/neon-alert/css/index.tsx +53 -0
- package/components/neon/neon-alert/css/neon-alert.module.css +60 -0
- package/components/neon/neon-alert/tailwind/index.tsx +59 -0
- package/components/neon/neon-badge/css/index.tsx +49 -0
- package/components/neon/neon-badge/css/neon-badge.module.css +53 -0
- package/components/neon/neon-badge/tailwind/index.tsx +50 -0
- package/components/neon/neon-button/css/index.tsx +54 -0
- package/components/neon/neon-button/css/neon-button.module.css +114 -0
- package/components/neon/neon-button/tailwind/index.tsx +51 -0
- package/components/neon/neon-divider/css/index.tsx +26 -0
- package/components/neon/neon-divider/css/neon-divider.module.css +43 -0
- package/components/neon/neon-divider/tailwind/index.tsx +36 -0
- package/components/neon/neon-progress/css/index.tsx +65 -0
- package/components/neon/neon-progress/css/neon-progress.module.css +88 -0
- package/components/neon/neon-progress/tailwind/index.tsx +46 -0
- package/components/neon/neon-tabs/css/index.tsx +41 -0
- package/components/neon/neon-tabs/css/neon-tabs.module.css +45 -0
- package/components/neon/neon-tabs/tailwind/index.tsx +53 -0
- package/components/neon/neon-toggle/css/index.tsx +58 -0
- package/components/neon/neon-toggle/css/neon-toggle.module.css +79 -0
- package/components/neon/neon-toggle/tailwind/index.tsx +57 -0
- package/components/text/glitch-text/glitch-text.module.css +2 -2
- package/package.json +1 -1
- package/components/glow-orbs/glow-orbs.module.css +0 -31
- package/components/glow-orbs/index.tsx +0 -87
- package/components/light-beams/index.tsx +0 -80
- package/components/light-beams/light-beams.module.css +0 -27
- package/components/noise-canvas/index.tsx +0 -113
- package/components/noise-canvas/noise-canvas.module.css +0 -8
- package/components/package.json +0 -13
- package/components/particle-field/index.tsx +0 -81
- package/components/particle-field/particle-field.module.css +0 -31
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
.card {
|
|
2
|
+
--holo-color: #00f0ff;
|
|
3
|
+
--holo-bg: rgba(0, 240, 255, 0.05);
|
|
4
|
+
|
|
5
|
+
position: relative;
|
|
6
|
+
padding: 1.5rem;
|
|
7
|
+
background: var(--holo-bg);
|
|
8
|
+
border: 1px solid var(--holo-color);
|
|
9
|
+
border-radius: 0.25rem;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.card::before {
|
|
14
|
+
content: '';
|
|
15
|
+
position: absolute;
|
|
16
|
+
top: 0;
|
|
17
|
+
left: 0;
|
|
18
|
+
right: 0;
|
|
19
|
+
height: 2px;
|
|
20
|
+
background: linear-gradient(90deg, transparent, var(--holo-color), transparent);
|
|
21
|
+
animation: scan-line 3s linear infinite;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@keyframes scan-line {
|
|
25
|
+
0% { top: 0; opacity: 1; }
|
|
26
|
+
50% { opacity: 0.5; }
|
|
27
|
+
100% { top: 100%; opacity: 1; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Variants */
|
|
31
|
+
.cyan {
|
|
32
|
+
--holo-color: #00f0ff;
|
|
33
|
+
--holo-bg: rgba(0, 240, 255, 0.05);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.pink {
|
|
37
|
+
--holo-color: #ff00ff;
|
|
38
|
+
--holo-bg: rgba(255, 0, 255, 0.05);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.green {
|
|
42
|
+
--holo-color: #00ff88;
|
|
43
|
+
--holo-bg: rgba(0, 255, 136, 0.05);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.purple {
|
|
47
|
+
--holo-color: #a855f7;
|
|
48
|
+
--holo-bg: rgba(168, 85, 247, 0.05);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.content {
|
|
52
|
+
position: relative;
|
|
53
|
+
z-index: 2;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.scanlines {
|
|
57
|
+
position: absolute;
|
|
58
|
+
top: 0;
|
|
59
|
+
left: 0;
|
|
60
|
+
width: 100%;
|
|
61
|
+
height: 100%;
|
|
62
|
+
background: repeating-linear-gradient(
|
|
63
|
+
0deg,
|
|
64
|
+
transparent,
|
|
65
|
+
transparent 2px,
|
|
66
|
+
rgba(0, 0, 0, 0.1) 2px,
|
|
67
|
+
rgba(0, 0, 0, 0.1) 4px
|
|
68
|
+
);
|
|
69
|
+
pointer-events: none;
|
|
70
|
+
z-index: 3;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.glow {
|
|
74
|
+
position: absolute;
|
|
75
|
+
top: -50%;
|
|
76
|
+
left: -50%;
|
|
77
|
+
width: 200%;
|
|
78
|
+
height: 200%;
|
|
79
|
+
background: radial-gradient(circle, var(--holo-bg) 0%, transparent 70%);
|
|
80
|
+
opacity: 0.5;
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
z-index: 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.flicker {
|
|
86
|
+
animation: hologram-flicker 4s infinite;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@keyframes hologram-flicker {
|
|
90
|
+
0%, 100% { opacity: 1; }
|
|
91
|
+
92% { opacity: 1; }
|
|
92
|
+
93% { opacity: 0.8; }
|
|
93
|
+
94% { opacity: 1; }
|
|
94
|
+
95% { opacity: 0.9; }
|
|
95
|
+
96% { opacity: 1; }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.card:hover {
|
|
99
|
+
box-shadow:
|
|
100
|
+
0 0 20px var(--holo-color),
|
|
101
|
+
inset 0 0 20px rgba(0, 240, 255, 0.1);
|
|
102
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, HTMLAttributes } from 'react';
|
|
4
|
+
import styles from './hologram-card.module.css';
|
|
5
|
+
|
|
6
|
+
export interface HologramCardProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Neon color variant */
|
|
8
|
+
variant?: 'cyan' | 'pink' | 'green' | 'purple';
|
|
9
|
+
/** Show CRT scanlines */
|
|
10
|
+
scanlines?: boolean;
|
|
11
|
+
/** Flicker effect */
|
|
12
|
+
flicker?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const HologramCard = forwardRef<HTMLDivElement, HologramCardProps>(
|
|
16
|
+
(
|
|
17
|
+
{
|
|
18
|
+
children,
|
|
19
|
+
variant = 'cyan',
|
|
20
|
+
scanlines = true,
|
|
21
|
+
flicker = false,
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
},
|
|
25
|
+
ref
|
|
26
|
+
) => {
|
|
27
|
+
const classes = [
|
|
28
|
+
styles.card,
|
|
29
|
+
styles[variant],
|
|
30
|
+
flicker && styles.flicker,
|
|
31
|
+
className,
|
|
32
|
+
].filter(Boolean).join(' ');
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div ref={ref} className={classes} {...props}>
|
|
36
|
+
<div className={styles.content}>{children}</div>
|
|
37
|
+
{scanlines && <div className={styles.scanlines} />}
|
|
38
|
+
<div className={styles.glow} />
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
HologramCard.displayName = 'HologramCard';
|
|
45
|
+
|
|
46
|
+
export default HologramCard;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, HTMLAttributes, useId } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface HologramCardProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
variant?: 'cyan' | 'pink' | 'green' | 'purple';
|
|
7
|
+
scanlines?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const variantStyles = {
|
|
11
|
+
cyan: { border: 'border-cyan-400', bg: 'bg-cyan-400/5', glow: 'hover:shadow-[0_0_20px_#00f0ff]', color: '#00f0ff' },
|
|
12
|
+
pink: { border: 'border-fuchsia-500', bg: 'bg-fuchsia-500/5', glow: 'hover:shadow-[0_0_20px_#ff00ff]', color: '#ff00ff' },
|
|
13
|
+
green: { border: 'border-emerald-400', bg: 'bg-emerald-400/5', glow: 'hover:shadow-[0_0_20px_#00ff88]', color: '#00ff88' },
|
|
14
|
+
purple: { border: 'border-purple-500', bg: 'bg-purple-500/5', glow: 'hover:shadow-[0_0_20px_#a855f7]', color: '#a855f7' },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const HologramCard = forwardRef<HTMLDivElement, HologramCardProps>(
|
|
18
|
+
({ children, variant = 'cyan', scanlines = true, className = '', ...props }, ref) => {
|
|
19
|
+
const id = useId();
|
|
20
|
+
const styles = variantStyles[variant];
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<style>{`
|
|
25
|
+
@keyframes scan-${id} {
|
|
26
|
+
0% { top: 0; opacity: 1; }
|
|
27
|
+
50% { opacity: 0.5; }
|
|
28
|
+
100% { top: 100%; opacity: 1; }
|
|
29
|
+
}
|
|
30
|
+
`}</style>
|
|
31
|
+
<div
|
|
32
|
+
ref={ref}
|
|
33
|
+
className={`
|
|
34
|
+
relative p-6 rounded border ${styles.border} ${styles.bg}
|
|
35
|
+
overflow-hidden transition-shadow duration-300 ${styles.glow}
|
|
36
|
+
${className}
|
|
37
|
+
`}
|
|
38
|
+
{...props}
|
|
39
|
+
>
|
|
40
|
+
<div
|
|
41
|
+
className="absolute left-0 right-0 h-0.5"
|
|
42
|
+
style={{
|
|
43
|
+
background: `linear-gradient(90deg, transparent, ${styles.color}, transparent)`,
|
|
44
|
+
animation: `scan-${id} 3s linear infinite`,
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
<div className="relative z-10">{children}</div>
|
|
48
|
+
{scanlines && (
|
|
49
|
+
<div
|
|
50
|
+
className="absolute inset-0 pointer-events-none z-20"
|
|
51
|
+
style={{ background: 'repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.1) 2px, rgba(0,0,0,0.1) 4px)' }}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
</>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
HologramCard.displayName = 'HologramCard';
|
|
61
|
+
export default HologramCard;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
.menu {
|
|
2
|
+
--hex-color: #00f0ff;
|
|
3
|
+
--hex-size: 5rem;
|
|
4
|
+
--hex-offset: 2.5rem;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-wrap: wrap;
|
|
7
|
+
gap: 0.5rem;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.cyan { --hex-color: #00f0ff; }
|
|
12
|
+
.pink { --hex-color: #ff00ff; }
|
|
13
|
+
.green { --hex-color: #00ff88; }
|
|
14
|
+
.purple { --hex-color: #a855f7; }
|
|
15
|
+
|
|
16
|
+
.sm { --hex-size: 3.75rem; --hex-offset: 1.875rem; }
|
|
17
|
+
.md { --hex-size: 5rem; --hex-offset: 2.5rem; }
|
|
18
|
+
.lg { --hex-size: 6.25rem; --hex-offset: 3.125rem; }
|
|
19
|
+
|
|
20
|
+
.item {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
width: var(--hex-size);
|
|
26
|
+
height: calc(var(--hex-size) * 1.1547);
|
|
27
|
+
padding: 0.5rem;
|
|
28
|
+
background: rgba(0, 0, 0, 0.6);
|
|
29
|
+
border: 2px solid var(--hex-color);
|
|
30
|
+
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
transition: all 0.3s ease;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.item:hover {
|
|
36
|
+
background: rgba(0, 240, 255, 0.2);
|
|
37
|
+
box-shadow: 0 0 20px var(--hex-color);
|
|
38
|
+
transform: scale(1.1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.icon {
|
|
42
|
+
font-size: calc(var(--hex-size) / 3);
|
|
43
|
+
color: var(--hex-color);
|
|
44
|
+
margin-bottom: 0.25rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.label {
|
|
48
|
+
font-family: var(--font-display, 'Orbitron', sans-serif);
|
|
49
|
+
font-size: 0.5625rem;
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
text-transform: uppercase;
|
|
52
|
+
letter-spacing: 0.0625em;
|
|
53
|
+
color: rgba(255, 255, 255, 0.9);
|
|
54
|
+
text-align: center;
|
|
55
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, HTMLAttributes } from 'react';
|
|
4
|
+
import styles from './hexagon-menu.module.css';
|
|
5
|
+
|
|
6
|
+
export interface HexagonMenuItem {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
icon?: React.ReactNode;
|
|
10
|
+
onClick?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface HexagonMenuProps extends HTMLAttributes<HTMLElement> {
|
|
14
|
+
items: HexagonMenuItem[];
|
|
15
|
+
variant?: 'cyan' | 'pink' | 'green' | 'purple';
|
|
16
|
+
size?: 'sm' | 'md' | 'lg';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const HexagonMenu = forwardRef<HTMLElement, HexagonMenuProps>(
|
|
20
|
+
({ items, variant = 'cyan', size = 'md', className, ...props }, ref) => {
|
|
21
|
+
return (
|
|
22
|
+
<nav ref={ref} className={`${styles.menu} ${styles[variant]} ${styles[size]} ${className || ''}`} {...props}>
|
|
23
|
+
{items.map((item, i) => (
|
|
24
|
+
<button key={item.id} className={styles.item} onClick={item.onClick} style={{ marginTop: i % 2 === 1 ? 'var(--hex-offset)' : 0 }}>
|
|
25
|
+
{item.icon && <span className={styles.icon}>{item.icon}</span>}
|
|
26
|
+
<span className={styles.label}>{item.label}</span>
|
|
27
|
+
</button>
|
|
28
|
+
))}
|
|
29
|
+
</nav>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
HexagonMenu.displayName = 'HexagonMenu';
|
|
35
|
+
export default HexagonMenu;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, HTMLAttributes } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface HexagonMenuItem {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
icon?: React.ReactNode;
|
|
9
|
+
onClick?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface HexagonMenuProps extends HTMLAttributes<HTMLElement> {
|
|
13
|
+
items: HexagonMenuItem[];
|
|
14
|
+
variant?: 'cyan' | 'pink' | 'green' | 'purple';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const variantStyles = {
|
|
18
|
+
cyan: { border: 'border-cyan-400', hover: 'hover:bg-cyan-400/20 hover:shadow-[0_0_20px_#00f0ff]', icon: 'text-cyan-400' },
|
|
19
|
+
pink: { border: 'border-fuchsia-500', hover: 'hover:bg-fuchsia-500/20 hover:shadow-[0_0_20px_#ff00ff]', icon: 'text-fuchsia-500' },
|
|
20
|
+
green: { border: 'border-emerald-400', hover: 'hover:bg-emerald-400/20 hover:shadow-[0_0_20px_#00ff88]', icon: 'text-emerald-400' },
|
|
21
|
+
purple: { border: 'border-purple-500', hover: 'hover:bg-purple-500/20 hover:shadow-[0_0_20px_#a855f7]', icon: 'text-purple-500' },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const HexagonMenu = forwardRef<HTMLElement, HexagonMenuProps>(
|
|
25
|
+
({ items, variant = 'cyan', className = '', ...props }, ref) => {
|
|
26
|
+
const colors = variantStyles[variant];
|
|
27
|
+
return (
|
|
28
|
+
<nav ref={ref} className={`flex flex-wrap gap-2 justify-center ${className}`} {...props}>
|
|
29
|
+
{items.map((item, i) => (
|
|
30
|
+
<button
|
|
31
|
+
key={item.id}
|
|
32
|
+
className={`
|
|
33
|
+
flex flex-col items-center justify-center w-20 h-[92px] p-2
|
|
34
|
+
bg-black/60 border-2 ${colors.border}
|
|
35
|
+
transition-all duration-300 cursor-pointer
|
|
36
|
+
${colors.hover} hover:scale-110 active:scale-105
|
|
37
|
+
`}
|
|
38
|
+
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)', marginTop: i % 2 === 1 ? '40px' : '0' }}
|
|
39
|
+
onClick={item.onClick}
|
|
40
|
+
>
|
|
41
|
+
{item.icon && <span className={`text-2xl ${colors.icon} mb-1`}>{item.icon}</span>}
|
|
42
|
+
<span className="font-['Orbitron',sans-serif] text-[9px] font-semibold uppercase tracking-wide text-white/90 text-center">
|
|
43
|
+
{item.label}
|
|
44
|
+
</span>
|
|
45
|
+
</button>
|
|
46
|
+
))}
|
|
47
|
+
</nav>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
HexagonMenu.displayName = 'HexagonMenu';
|
|
53
|
+
export default HexagonMenu;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, HTMLAttributes } from 'react';
|
|
4
|
+
import styles from './neon-alert.module.css';
|
|
5
|
+
|
|
6
|
+
export interface NeonAlertProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Alert type */
|
|
8
|
+
variant?: 'info' | 'success' | 'warning' | 'error';
|
|
9
|
+
/** Alert title */
|
|
10
|
+
title?: string;
|
|
11
|
+
/** Dismissible with close button */
|
|
12
|
+
dismissible?: boolean;
|
|
13
|
+
/** Close callback */
|
|
14
|
+
onDismiss?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const NeonAlert = forwardRef<HTMLDivElement, NeonAlertProps>(
|
|
18
|
+
(
|
|
19
|
+
{
|
|
20
|
+
children,
|
|
21
|
+
variant = 'info',
|
|
22
|
+
title,
|
|
23
|
+
dismissible = false,
|
|
24
|
+
onDismiss,
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
},
|
|
28
|
+
ref
|
|
29
|
+
) => {
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
ref={ref}
|
|
33
|
+
className={`${styles.alert} ${styles[variant]} ${className || ''}`}
|
|
34
|
+
role="alert"
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
<div className={styles.content}>
|
|
38
|
+
{title && <div className={styles.title}>{title}</div>}
|
|
39
|
+
<div className={styles.message}>{children}</div>
|
|
40
|
+
</div>
|
|
41
|
+
{dismissible && (
|
|
42
|
+
<button className={styles.dismiss} onClick={onDismiss} aria-label="Dismiss">
|
|
43
|
+
✕
|
|
44
|
+
</button>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
NeonAlert.displayName = 'NeonAlert';
|
|
52
|
+
|
|
53
|
+
export default NeonAlert;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
.alert {
|
|
2
|
+
--alert-color: #00f0ff;
|
|
3
|
+
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: flex-start;
|
|
6
|
+
gap: 0.75rem;
|
|
7
|
+
padding: 1rem;
|
|
8
|
+
background: rgba(0, 0, 0, 0.5);
|
|
9
|
+
border: 1px solid var(--alert-color);
|
|
10
|
+
border-left: 4px solid var(--alert-color);
|
|
11
|
+
box-shadow: 0 0 10px rgba(0, 240, 255, 0.2);
|
|
12
|
+
animation: alert-slide 0.3s ease-out;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@keyframes alert-slide {
|
|
16
|
+
from { opacity: 0; transform: translateX(-20px); }
|
|
17
|
+
to { opacity: 1; transform: translateX(0); }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.info { --alert-color: #00f0ff; }
|
|
21
|
+
.success { --alert-color: #00ff88; }
|
|
22
|
+
.warning { --alert-color: #ffaa00; }
|
|
23
|
+
.error { --alert-color: #ff0040; }
|
|
24
|
+
|
|
25
|
+
.content {
|
|
26
|
+
flex: 1;
|
|
27
|
+
min-width: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.title {
|
|
31
|
+
margin-bottom: 0.25rem;
|
|
32
|
+
font-family: var(--font-display, 'Orbitron', sans-serif);
|
|
33
|
+
font-size: 0.8125rem;
|
|
34
|
+
font-weight: 700;
|
|
35
|
+
text-transform: uppercase;
|
|
36
|
+
letter-spacing: 0.0625em;
|
|
37
|
+
color: var(--alert-color);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.message {
|
|
41
|
+
font-family: var(--font-body, 'Rajdhani', sans-serif);
|
|
42
|
+
font-size: 0.875rem;
|
|
43
|
+
color: rgba(255, 255, 255, 0.8);
|
|
44
|
+
line-height: 1.5;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.dismiss {
|
|
48
|
+
flex-shrink: 0;
|
|
49
|
+
padding: 0.25rem 0.5rem;
|
|
50
|
+
font-size: 0.875rem;
|
|
51
|
+
color: rgba(255, 255, 255, 0.5);
|
|
52
|
+
background: transparent;
|
|
53
|
+
border: none;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
transition: color 0.2s ease;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.dismiss:hover {
|
|
59
|
+
color: var(--alert-color);
|
|
60
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, HTMLAttributes } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface NeonAlertProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
variant?: 'info' | 'success' | 'warning' | 'error';
|
|
7
|
+
title?: string;
|
|
8
|
+
dismissible?: boolean;
|
|
9
|
+
onDismiss?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const variantStyles = {
|
|
13
|
+
info: 'border-cyan-400 shadow-[0_0_10px_rgba(0,240,255,0.2)] [&_.title]:text-cyan-400',
|
|
14
|
+
success: 'border-emerald-400 shadow-[0_0_10px_rgba(0,255,136,0.2)] [&_.title]:text-emerald-400',
|
|
15
|
+
warning: 'border-amber-400 shadow-[0_0_10px_rgba(255,170,0,0.2)] [&_.title]:text-amber-400',
|
|
16
|
+
error: 'border-rose-500 shadow-[0_0_10px_rgba(255,0,64,0.2)] [&_.title]:text-rose-500',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const NeonAlert = forwardRef<HTMLDivElement, NeonAlertProps>(
|
|
20
|
+
({ children, variant = 'info', title, dismissible = false, onDismiss, className = '', ...props }, ref) => {
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
ref={ref}
|
|
24
|
+
role="alert"
|
|
25
|
+
className={`
|
|
26
|
+
flex items-start gap-3 p-4
|
|
27
|
+
bg-black/50 border border-l-4
|
|
28
|
+
animate-[slideIn_0.3s_ease-out]
|
|
29
|
+
${variantStyles[variant]}
|
|
30
|
+
${className}
|
|
31
|
+
`}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
<div className="flex-1 min-w-0">
|
|
35
|
+
{title && (
|
|
36
|
+
<div className="title mb-1 font-['Orbitron',sans-serif] text-[13px] font-bold uppercase tracking-wide">
|
|
37
|
+
{title}
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
<div className="font-['Rajdhani',sans-serif] text-sm text-white/80 leading-relaxed">
|
|
41
|
+
{children}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
{dismissible && (
|
|
45
|
+
<button
|
|
46
|
+
className="flex-shrink-0 px-2 py-1 text-sm text-white/50 hover:text-white bg-transparent border-none cursor-pointer transition-colors"
|
|
47
|
+
onClick={onDismiss}
|
|
48
|
+
aria-label="Dismiss"
|
|
49
|
+
>
|
|
50
|
+
✕
|
|
51
|
+
</button>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
NeonAlert.displayName = 'NeonAlert';
|
|
59
|
+
export default NeonAlert;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, HTMLAttributes } from 'react';
|
|
4
|
+
import styles from './neon-badge.module.css';
|
|
5
|
+
|
|
6
|
+
export interface NeonBadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
7
|
+
/** Neon color variant */
|
|
8
|
+
variant?: 'cyan' | 'pink' | 'green' | 'purple' | 'red' | 'yellow';
|
|
9
|
+
/** Badge size */
|
|
10
|
+
size?: 'sm' | 'md' | 'lg';
|
|
11
|
+
/** Continuous pulse animation */
|
|
12
|
+
pulse?: boolean;
|
|
13
|
+
/** Outline style instead of filled */
|
|
14
|
+
outline?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const NeonBadge = forwardRef<HTMLSpanElement, NeonBadgeProps>(
|
|
18
|
+
(
|
|
19
|
+
{
|
|
20
|
+
children,
|
|
21
|
+
variant = 'cyan',
|
|
22
|
+
size = 'md',
|
|
23
|
+
pulse = false,
|
|
24
|
+
outline = false,
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
},
|
|
28
|
+
ref
|
|
29
|
+
) => {
|
|
30
|
+
const classes = [
|
|
31
|
+
styles.badge,
|
|
32
|
+
styles[variant],
|
|
33
|
+
styles[size],
|
|
34
|
+
pulse && styles.pulse,
|
|
35
|
+
outline && styles.outline,
|
|
36
|
+
className,
|
|
37
|
+
].filter(Boolean).join(' ');
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<span ref={ref} className={classes} {...props}>
|
|
41
|
+
{children}
|
|
42
|
+
</span>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
NeonBadge.displayName = 'NeonBadge';
|
|
48
|
+
|
|
49
|
+
export default NeonBadge;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.badge {
|
|
2
|
+
--neon-color: #00f0ff;
|
|
3
|
+
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
padding: 0.25rem 0.75rem;
|
|
8
|
+
font-family: var(--font-display, 'Orbitron', sans-serif);
|
|
9
|
+
font-size: 0.6875rem;
|
|
10
|
+
font-weight: 700;
|
|
11
|
+
text-transform: uppercase;
|
|
12
|
+
letter-spacing: 0.0625em;
|
|
13
|
+
color: hsl(var(--background, 0 0% 4%));
|
|
14
|
+
background: var(--neon-color);
|
|
15
|
+
border-radius: 2px;
|
|
16
|
+
box-shadow: 0 0 10px var(--neon-color);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Variants */
|
|
20
|
+
.cyan { --neon-color: #00f0ff; }
|
|
21
|
+
.pink { --neon-color: #ff00ff; }
|
|
22
|
+
.green { --neon-color: #00ff88; }
|
|
23
|
+
.purple { --neon-color: #a855f7; }
|
|
24
|
+
.red { --neon-color: #ff0040; }
|
|
25
|
+
.yellow { --neon-color: #ffff00; }
|
|
26
|
+
|
|
27
|
+
/* Sizes */
|
|
28
|
+
.sm {
|
|
29
|
+
padding: 0.125rem 0.5rem;
|
|
30
|
+
font-size: 0.5625rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.lg {
|
|
34
|
+
padding: 0.375rem 1rem;
|
|
35
|
+
font-size: 0.8125rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Outline variant */
|
|
39
|
+
.outline {
|
|
40
|
+
color: var(--neon-color);
|
|
41
|
+
background: transparent;
|
|
42
|
+
border: 1px solid var(--neon-color);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Pulse animation */
|
|
46
|
+
.pulse {
|
|
47
|
+
animation: badge-pulse 2s ease-in-out infinite;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@keyframes badge-pulse {
|
|
51
|
+
0%, 100% { box-shadow: 0 0 5px var(--neon-color); }
|
|
52
|
+
50% { box-shadow: 0 0 20px var(--neon-color), 0 0 30px var(--neon-color); }
|
|
53
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, HTMLAttributes } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface NeonBadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
6
|
+
variant?: 'cyan' | 'pink' | 'green' | 'purple' | 'red' | 'yellow';
|
|
7
|
+
size?: 'sm' | 'md' | 'lg';
|
|
8
|
+
pulse?: boolean;
|
|
9
|
+
outline?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const variantStyles = {
|
|
13
|
+
cyan: { solid: 'bg-cyan-400 shadow-[0_0_10px_#00f0ff]', outline: 'text-cyan-400 border-cyan-400' },
|
|
14
|
+
pink: { solid: 'bg-fuchsia-500 shadow-[0_0_10px_#ff00ff]', outline: 'text-fuchsia-500 border-fuchsia-500' },
|
|
15
|
+
green: { solid: 'bg-emerald-400 shadow-[0_0_10px_#00ff88]', outline: 'text-emerald-400 border-emerald-400' },
|
|
16
|
+
purple: { solid: 'bg-purple-500 shadow-[0_0_10px_#a855f7]', outline: 'text-purple-500 border-purple-500' },
|
|
17
|
+
red: { solid: 'bg-rose-500 shadow-[0_0_10px_#ff0040]', outline: 'text-rose-500 border-rose-500' },
|
|
18
|
+
yellow: { solid: 'bg-yellow-400 shadow-[0_0_10px_#ffff00]', outline: 'text-yellow-400 border-yellow-400' },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const sizeStyles = {
|
|
22
|
+
sm: 'px-2 py-0.5 text-[9px]',
|
|
23
|
+
md: 'px-3 py-1 text-[11px]',
|
|
24
|
+
lg: 'px-4 py-1.5 text-[13px]',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const NeonBadge = forwardRef<HTMLSpanElement, NeonBadgeProps>(
|
|
28
|
+
({ children, variant = 'cyan', size = 'md', pulse = false, outline = false, className = '', ...props }, ref) => {
|
|
29
|
+
const colors = variantStyles[variant];
|
|
30
|
+
return (
|
|
31
|
+
<span
|
|
32
|
+
ref={ref}
|
|
33
|
+
className={`
|
|
34
|
+
inline-flex items-center justify-center
|
|
35
|
+
font-['Orbitron',sans-serif] font-bold uppercase tracking-wide rounded-sm
|
|
36
|
+
${sizeStyles[size]}
|
|
37
|
+
${outline ? `bg-transparent border ${colors.outline}` : `text-[#0a0a0f] ${colors.solid}`}
|
|
38
|
+
${pulse ? 'animate-pulse' : ''}
|
|
39
|
+
${className}
|
|
40
|
+
`}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</span>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
NeonBadge.displayName = 'NeonBadge';
|
|
50
|
+
export default NeonBadge;
|