@checkstack/ui 1.2.1 → 1.3.1
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/CHANGELOG.md +39 -0
- package/package.json +3 -3
- package/src/components/AmbientBackground.tsx +41 -70
- package/src/components/IDELayout/index.tsx +200 -0
- package/src/index.ts +1 -0
- package/src/themes.css +49 -47
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# @checkstack/ui
|
|
2
2
|
|
|
3
|
+
## 1.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 765b764: Optimize AmbientBackground performance by replacing thousand-div grid with a single-element CSS mask and hardware-accelerated Aurora Mesh animations.
|
|
8
|
+
|
|
9
|
+
## 1.3.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 26d8bae: Distributed satellite health checks and Assignment IDE page
|
|
14
|
+
|
|
15
|
+
**Satellite System**
|
|
16
|
+
|
|
17
|
+
- New `satellite-backend`, `satellite-common`, `satellite-frontend`, and `satellite` agent packages for distributed health check execution
|
|
18
|
+
- WebSocket-based satellite connectivity with authentication, heartbeats, and live configuration push
|
|
19
|
+
- Satellite management UI with create dialog, status badges, and list page
|
|
20
|
+
|
|
21
|
+
**Live Configuration Updates**
|
|
22
|
+
|
|
23
|
+
- Added `assignmentChanged` hook to `healthcheck-backend` for cross-plugin communication
|
|
24
|
+
- `satellite-backend` subscribes to assignment changes and pushes config updates to connected satellites in real-time
|
|
25
|
+
|
|
26
|
+
**Assignment IDE Page**
|
|
27
|
+
|
|
28
|
+
- Replaced the 1028-line modal-based `SystemHealthCheckAssignment` component with a full-page IDE layout
|
|
29
|
+
- New modular components: `AssignmentTree`, `GeneralPanel`, `ThresholdsPanel`, `RetentionPanel`, `ExecutionPanel`
|
|
30
|
+
- Added unassign capability and sorted assignment lists for stable ordering
|
|
31
|
+
|
|
32
|
+
**Shared IDE Primitives**
|
|
33
|
+
|
|
34
|
+
- Extracted `IDETreeNode`, `IDETreeSection`, `IDEStatusBar`, `IDELayout` to `@checkstack/ui` for cross-plugin reuse
|
|
35
|
+
- Migrated existing health check IDE editor to use shared primitives
|
|
36
|
+
|
|
37
|
+
**Infrastructure**
|
|
38
|
+
|
|
39
|
+
- Added `Dockerfile.satellite` for containerized satellite deployment
|
|
40
|
+
- WebSocket route registry in `@checkstack/backend` and `@checkstack/backend-api`
|
|
41
|
+
|
|
3
42
|
## 1.2.1
|
|
4
43
|
|
|
5
44
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@checkstack/common": "0.6.
|
|
8
|
-
"@checkstack/frontend-api": "0.3.
|
|
7
|
+
"@checkstack/common": "0.6.5",
|
|
8
|
+
"@checkstack/frontend-api": "0.3.9",
|
|
9
9
|
"@monaco-editor/react": "^4.7.0",
|
|
10
10
|
"@radix-ui/react-accordion": "^1.2.12",
|
|
11
11
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import { cn } from "../utils";
|
|
3
3
|
|
|
4
4
|
interface AmbientBackgroundProps {
|
|
@@ -6,53 +6,15 @@ interface AmbientBackgroundProps {
|
|
|
6
6
|
className?: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
const TILE_SIZE = 48;
|
|
10
|
-
|
|
11
9
|
/**
|
|
12
|
-
* AmbientBackground -
|
|
13
|
-
* Features
|
|
14
|
-
*
|
|
10
|
+
* AmbientBackground - High-performance background pattern
|
|
11
|
+
* Features an "Inverse Glow Grid" where aurora effects shine through transparent grid lines.
|
|
12
|
+
* Provides a premium "glowing grid" aesthetic with minimal performance impact.
|
|
15
13
|
*/
|
|
16
14
|
export const AmbientBackground: React.FC<AmbientBackgroundProps> = ({
|
|
17
15
|
children,
|
|
18
16
|
className,
|
|
19
17
|
}) => {
|
|
20
|
-
const [dimensions, setDimensions] = useState({ cols: 40, rows: 25 });
|
|
21
|
-
|
|
22
|
-
// Calculate grid size based on viewport
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const updateDimensions = () => {
|
|
25
|
-
const cols = Math.ceil(globalThis.innerWidth / TILE_SIZE) + 2;
|
|
26
|
-
const rows = Math.ceil(globalThis.innerHeight / TILE_SIZE) + 2;
|
|
27
|
-
setDimensions({ cols, rows });
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
updateDimensions();
|
|
31
|
-
globalThis.addEventListener("resize", updateDimensions);
|
|
32
|
-
return () => globalThis.removeEventListener("resize", updateDimensions);
|
|
33
|
-
}, []);
|
|
34
|
-
|
|
35
|
-
// Generate tile grid with staggered animation delays
|
|
36
|
-
const tiles = useMemo(() => {
|
|
37
|
-
const { cols, rows } = dimensions;
|
|
38
|
-
const result: Array<{ key: string; delay: number; isLight: boolean }> = [];
|
|
39
|
-
|
|
40
|
-
for (let row = 0; row < rows; row++) {
|
|
41
|
-
for (let col = 0; col < cols; col++) {
|
|
42
|
-
const isLight = (row + col) % 2 === 0;
|
|
43
|
-
// Negative delay so tiles start at different points in their 12s cycle
|
|
44
|
-
// This creates a gentle, staggered breathing effect across the grid
|
|
45
|
-
const delay = -((row * 7 + col * 13 + row * col) % 24) * 0.5;
|
|
46
|
-
result.push({
|
|
47
|
-
key: `${row}-${col}`,
|
|
48
|
-
delay,
|
|
49
|
-
isLight,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return result;
|
|
54
|
-
}, [dimensions]);
|
|
55
|
-
|
|
56
18
|
return (
|
|
57
19
|
<div
|
|
58
20
|
className={cn(
|
|
@@ -60,40 +22,49 @@ export const AmbientBackground: React.FC<AmbientBackgroundProps> = ({
|
|
|
60
22
|
className
|
|
61
23
|
)}
|
|
62
24
|
>
|
|
63
|
-
{/*
|
|
64
|
-
<div
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
25
|
+
{/* Performance optimized background layers */}
|
|
26
|
+
<div className="pointer-events-none fixed inset-0 overflow-hidden">
|
|
27
|
+
{/* Layer 1: Aurora Blobs (Bottom) - Hardware accelerated via transform/compositor */}
|
|
28
|
+
<div
|
|
29
|
+
className="aurora-blob absolute w-[50%] h-[50%] -top-[10%] -left-[10%]"
|
|
30
|
+
style={{
|
|
31
|
+
background: "radial-gradient(circle at center, hsl(var(--primary) / 0.8), transparent 60%)",
|
|
32
|
+
animation: "aurora-float-1 25s ease-in-out infinite",
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
<div
|
|
36
|
+
className="aurora-blob absolute w-[40%] h-[40%] bottom-[10%] right-[10%]"
|
|
37
|
+
style={{
|
|
38
|
+
background: "radial-gradient(circle at center, hsl(var(--chart-2) / 0.7), transparent 60%)",
|
|
39
|
+
animation: "aurora-float-2 20s ease-in-out infinite",
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
<div
|
|
43
|
+
className="aurora-blob absolute w-[35%] h-[35%] top-[30%] left-[40%]"
|
|
44
|
+
style={{
|
|
45
|
+
background: "radial-gradient(circle at center, hsl(var(--primary) / 0.6), transparent 60%)",
|
|
46
|
+
animation: "aurora-float-3 30s ease-in-out infinite",
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
<div
|
|
50
|
+
className="aurora-blob absolute w-[45%] h-[45%] bottom-[20%] left-[10%]"
|
|
51
|
+
style={{
|
|
52
|
+
background: "radial-gradient(circle at center, hsl(var(--chart-1) / 0.5), transparent 60%)",
|
|
53
|
+
animation: "aurora-float-4 35s ease-in-out infinite",
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
{/* Layer 2: Inverse Grid Mask (Top) - Transparent lines on a solid background */}
|
|
58
|
+
<div className="ambient-grid-inverse absolute inset-0 overflow-hidden" />
|
|
88
59
|
</div>
|
|
89
60
|
|
|
90
|
-
{/* Edge vignette to fade out the
|
|
61
|
+
{/* Layer 3: Edge vignette to fade out the background smoothly */}
|
|
91
62
|
<div
|
|
92
63
|
className="pointer-events-none fixed inset-0"
|
|
93
64
|
style={{
|
|
94
65
|
background: `
|
|
95
|
-
linear-gradient(to right, hsl(var(--background)) 0%, transparent
|
|
96
|
-
linear-gradient(to bottom, hsl(var(--background)) 0%, transparent
|
|
66
|
+
linear-gradient(to right, hsl(var(--background)) 0%, transparent 15%, transparent 85%, hsl(var(--background)) 100%),
|
|
67
|
+
linear-gradient(to bottom, hsl(var(--background)) 0%, transparent 15%, transparent 85%, hsl(var(--background)) 100%)
|
|
97
68
|
`,
|
|
98
69
|
}}
|
|
99
70
|
/>
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AlertCircle, CheckCircle2 } from "lucide-react";
|
|
3
|
+
import { Badge } from "../Badge";
|
|
4
|
+
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// SHARED TYPES
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generic validation issue for IDE-style editors.
|
|
11
|
+
* nodeId identifies which tree node the issue belongs to.
|
|
12
|
+
*/
|
|
13
|
+
export interface ValidationIssue {
|
|
14
|
+
nodeId: string;
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// VALIDATION DOT
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Small red dot indicator shown on tree nodes with validation issues.
|
|
24
|
+
*/
|
|
25
|
+
export function IDEValidationDot({
|
|
26
|
+
nodeId,
|
|
27
|
+
issues,
|
|
28
|
+
}: {
|
|
29
|
+
nodeId: string;
|
|
30
|
+
issues: ValidationIssue[];
|
|
31
|
+
}) {
|
|
32
|
+
const nodeIssues = issues.filter((i) => i.nodeId === nodeId);
|
|
33
|
+
if (nodeIssues.length === 0) return;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<span className="ml-auto flex h-2 w-2 rounded-full bg-destructive shrink-0" />
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// TREE NODE
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
44
|
+
interface IDETreeNodeProps {
|
|
45
|
+
nodeId: string;
|
|
46
|
+
label: string;
|
|
47
|
+
icon: React.ElementType;
|
|
48
|
+
selected: boolean;
|
|
49
|
+
onClick: () => void;
|
|
50
|
+
issues?: ValidationIssue[];
|
|
51
|
+
indent?: boolean;
|
|
52
|
+
badge?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generic tree node for IDE-style left panel navigation.
|
|
57
|
+
* Highlights when selected, shows validation dots and optional badges.
|
|
58
|
+
*/
|
|
59
|
+
export const IDETreeNode: React.FC<IDETreeNodeProps> = ({
|
|
60
|
+
nodeId,
|
|
61
|
+
label,
|
|
62
|
+
icon: Icon,
|
|
63
|
+
selected,
|
|
64
|
+
onClick,
|
|
65
|
+
issues = [],
|
|
66
|
+
indent = false,
|
|
67
|
+
badge,
|
|
68
|
+
}) => {
|
|
69
|
+
return (
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
onClick={onClick}
|
|
73
|
+
className={`flex items-center gap-2 w-full px-3 py-2 text-sm text-left transition-colors ${
|
|
74
|
+
indent ? "pl-7" : ""
|
|
75
|
+
} ${
|
|
76
|
+
selected
|
|
77
|
+
? "bg-primary/10 text-primary border-l-2 border-primary"
|
|
78
|
+
: "hover:bg-muted/50 border-l-2 border-transparent"
|
|
79
|
+
}`}
|
|
80
|
+
>
|
|
81
|
+
<Icon className="h-4 w-4 shrink-0 opacity-60" />
|
|
82
|
+
<span className="truncate flex-1">{label}</span>
|
|
83
|
+
{badge && (
|
|
84
|
+
<Badge variant="secondary" className="text-[10px] shrink-0">
|
|
85
|
+
{badge}
|
|
86
|
+
</Badge>
|
|
87
|
+
)}
|
|
88
|
+
<IDEValidationDot nodeId={nodeId} issues={issues} />
|
|
89
|
+
</button>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// =============================================================================
|
|
94
|
+
// TREE SECTION HEADER
|
|
95
|
+
// =============================================================================
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Section header for grouping tree nodes (e.g., "Check Items", "Permissions").
|
|
99
|
+
*/
|
|
100
|
+
export const IDETreeSection: React.FC<{ label: string }> = ({ label }) => (
|
|
101
|
+
<div className="px-3 pt-4 pb-1">
|
|
102
|
+
<span className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider">
|
|
103
|
+
{label}
|
|
104
|
+
</span>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// =============================================================================
|
|
109
|
+
// STATUS BAR
|
|
110
|
+
// =============================================================================
|
|
111
|
+
|
|
112
|
+
interface IDEStatusBarProps {
|
|
113
|
+
issues: ValidationIssue[];
|
|
114
|
+
onIssueClick: (nodeId: string) => void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Bottom status bar showing validation issue count with clickable navigation.
|
|
119
|
+
*/
|
|
120
|
+
export const IDEStatusBar: React.FC<IDEStatusBarProps> = ({
|
|
121
|
+
issues,
|
|
122
|
+
onIssueClick,
|
|
123
|
+
}) => {
|
|
124
|
+
if (issues.length === 0) {
|
|
125
|
+
return (
|
|
126
|
+
<div className="flex items-center gap-2 px-4 py-2 mt-2 rounded-md border bg-card text-xs text-muted-foreground">
|
|
127
|
+
<CheckCircle2 className="h-3.5 w-3.5 text-green-500" />
|
|
128
|
+
<span>No issues found</span>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div className="flex items-center gap-3 px-4 py-2 mt-2 rounded-md border bg-card text-xs">
|
|
135
|
+
<div className="flex items-center gap-1.5 text-destructive shrink-0">
|
|
136
|
+
<AlertCircle className="h-3.5 w-3.5" />
|
|
137
|
+
<span className="font-medium">
|
|
138
|
+
{issues.length} {issues.length === 1 ? "issue" : "issues"}
|
|
139
|
+
</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div className="flex items-center gap-2 overflow-x-auto">
|
|
142
|
+
{issues.map((issue, i) => (
|
|
143
|
+
<button
|
|
144
|
+
key={`${issue.nodeId}-${i}`}
|
|
145
|
+
type="button"
|
|
146
|
+
onClick={() => onIssueClick(issue.nodeId)}
|
|
147
|
+
className="text-muted-foreground hover:text-foreground transition-colors whitespace-nowrap underline-offset-2 hover:underline"
|
|
148
|
+
>
|
|
149
|
+
{issue.message}
|
|
150
|
+
</button>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// =============================================================================
|
|
158
|
+
// IDE LAYOUT
|
|
159
|
+
// =============================================================================
|
|
160
|
+
|
|
161
|
+
interface IDELayoutProps {
|
|
162
|
+
/** Left panel tree content */
|
|
163
|
+
tree: React.ReactNode;
|
|
164
|
+
/** Right panel editor content */
|
|
165
|
+
panel: React.ReactNode;
|
|
166
|
+
/** Optional status bar issues (renders IDEStatusBar automatically) */
|
|
167
|
+
issues?: ValidationIssue[];
|
|
168
|
+
/** Callback when a status bar issue is clicked */
|
|
169
|
+
onIssueClick?: (nodeId: string) => void;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Two-column IDE layout shell: left tree panel + right editor panel + status bar.
|
|
174
|
+
* Used by health check configuration editor and assignment editor.
|
|
175
|
+
*/
|
|
176
|
+
export const IDELayout: React.FC<IDELayoutProps> = ({
|
|
177
|
+
tree,
|
|
178
|
+
panel,
|
|
179
|
+
issues,
|
|
180
|
+
onIssueClick,
|
|
181
|
+
}) => {
|
|
182
|
+
return (
|
|
183
|
+
<>
|
|
184
|
+
<div className="flex flex-col lg:flex-row gap-0 min-h-[60vh] border rounded-lg bg-card overflow-hidden">
|
|
185
|
+
{/* Explorer Tree — Left Panel */}
|
|
186
|
+
<div className="w-full lg:w-64 shrink-0 border-b lg:border-b-0 lg:border-r bg-muted/30">
|
|
187
|
+
{tree}
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
{/* Editor Panel — Right Panel */}
|
|
191
|
+
<div className="flex-1 min-w-0">{panel}</div>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{/* Status Bar */}
|
|
195
|
+
{issues && onIssueClick && (
|
|
196
|
+
<IDEStatusBar issues={issues} onIssueClick={onIssueClick} />
|
|
197
|
+
)}
|
|
198
|
+
</>
|
|
199
|
+
);
|
|
200
|
+
};
|
package/src/index.ts
CHANGED
package/src/themes.css
CHANGED
|
@@ -143,41 +143,15 @@
|
|
|
143
143
|
|
|
144
144
|
/* Custom animations */
|
|
145
145
|
@keyframes bell-ring {
|
|
146
|
-
0% {
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
transform: rotate(-15deg);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
30% {
|
|
159
|
-
transform: rotate(10deg);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
40% {
|
|
163
|
-
transform: rotate(-10deg);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
50% {
|
|
167
|
-
transform: rotate(5deg);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
60% {
|
|
171
|
-
transform: rotate(-5deg);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
70% {
|
|
175
|
-
transform: rotate(0deg);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
100% {
|
|
179
|
-
transform: rotate(0deg);
|
|
180
|
-
}
|
|
146
|
+
0% { transform: rotate(0deg); }
|
|
147
|
+
10% { transform: rotate(15deg); }
|
|
148
|
+
20% { transform: rotate(-15deg); }
|
|
149
|
+
30% { transform: rotate(10deg); }
|
|
150
|
+
40% { transform: rotate(-10deg); }
|
|
151
|
+
50% { transform: rotate(5deg); }
|
|
152
|
+
60% { transform: rotate(-5deg); }
|
|
153
|
+
70% { transform: rotate(0deg); }
|
|
154
|
+
100% { transform: rotate(0deg); }
|
|
181
155
|
}
|
|
182
156
|
|
|
183
157
|
.animate-bell-ring {
|
|
@@ -185,20 +159,48 @@
|
|
|
185
159
|
transform-origin: top center;
|
|
186
160
|
}
|
|
187
161
|
|
|
188
|
-
/*
|
|
189
|
-
@keyframes
|
|
162
|
+
/* High-performance ambient background styles */
|
|
163
|
+
@keyframes aurora-float-1 {
|
|
164
|
+
0% { transform: translate(-15vw, -10vh) rotate(0deg); }
|
|
165
|
+
50% { transform: translate(35vw, 25vh) rotate(180deg); }
|
|
166
|
+
100% { transform: translate(-15vw, -10vh) rotate(360deg); }
|
|
167
|
+
}
|
|
190
168
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
169
|
+
@keyframes aurora-float-2 {
|
|
170
|
+
0% { transform: translate(0, 0) scale(1); }
|
|
171
|
+
50% { transform: translate(-30vw, 25vh) scale(1.2); }
|
|
172
|
+
100% { transform: translate(0, 0) scale(1); }
|
|
173
|
+
}
|
|
195
174
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
175
|
+
@keyframes aurora-float-3 {
|
|
176
|
+
0% { transform: translate(0, 0) rotate(0deg); }
|
|
177
|
+
50% { transform: translate(25vw, -30vh) rotate(-180deg); }
|
|
178
|
+
100% { transform: translate(0, 0) rotate(-360deg); }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@keyframes aurora-float-4 {
|
|
182
|
+
0% { transform: translate(0, 0) scale(1.1); }
|
|
183
|
+
50% { transform: translate(-20vw, -20vh) scale(1); }
|
|
184
|
+
100% { transform: translate(0, 0) scale(1.1); }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.ambient-grid {
|
|
188
|
+
background-image:
|
|
189
|
+
linear-gradient(to right, hsl(var(--muted-foreground) / 0.15) 1px, transparent 1px),
|
|
190
|
+
linear-gradient(to bottom, hsl(var(--muted-foreground) / 0.15) 1px, transparent 1px);
|
|
191
|
+
background-size: 48px 48px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.ambient-grid-inverse {
|
|
195
|
+
background: hsl(var(--background));
|
|
196
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg width='48' height='48' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1' y='1' width='47' height='47' fill='black'/%3E%3C/svg%3E");
|
|
197
|
+
mask-image: url("data:image/svg+xml,%3Csvg width='48' height='48' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1' y='1' width='47' height='47' fill='black'/%3E%3C/svg%3E");
|
|
199
198
|
}
|
|
200
199
|
|
|
201
|
-
.
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
.aurora-blob {
|
|
201
|
+
position: absolute;
|
|
202
|
+
filter: blur(60px);
|
|
203
|
+
opacity: 0.6;
|
|
204
|
+
pointer-events: none;
|
|
205
|
+
will-change: transform;
|
|
204
206
|
}
|