Update .gitignore to include .cursorindexingignore and DESIGN-BRIEF.md
This commit is contained in:
parent
de940920fd
commit
1000fe89e3
2
.gitignore
vendored
2
.gitignore
vendored
@ -22,3 +22,5 @@ dump/**/nohup.out
|
||||
*.tmp
|
||||
*.swp
|
||||
*~
|
||||
.cursorindexingignore
|
||||
astro-website/DESIGN-BRIEF.md
|
||||
|
||||
4
.specstory/.gitignore
vendored
Normal file
4
.specstory/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# SpecStory project identity file
|
||||
/.project.json
|
||||
# SpecStory explanation file
|
||||
/.what-is-this.md
|
||||
24
astro-website/.gitignore
vendored
Normal file
24
astro-website/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
0
astro-website/README.md
Normal file
0
astro-website/README.md
Normal file
15
astro-website/astro.config.mjs
Normal file
15
astro-website/astro.config.mjs
Normal file
@ -0,0 +1,15 @@
|
||||
// @ts-check
|
||||
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import react from '@astrojs/react';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
|
||||
integrations: [react()],
|
||||
});
|
||||
22
astro-website/components.json
Normal file
22
astro-website/components.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/styles/global.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"registries": {}
|
||||
}
|
||||
7235
astro-website/package-lock.json
generated
Normal file
7235
astro-website/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
81
astro-website/package.json
Normal file
81
astro-website/package.json
Normal file
@ -0,0 +1,81 @@
|
||||
{
|
||||
"name": "nuke-telemetry",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.3.12",
|
||||
"@astrojs/react": "^4.4.2",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@types/canvas-confetti": "^1.9.0",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"astro": "^5.16.1",
|
||||
"canvas-confetti": "^1.9.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.555.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"@radix-ui/react-accordion": "1.2.2",
|
||||
"@radix-ui/react-alert-dialog": "1.1.4",
|
||||
"@radix-ui/react-aspect-ratio": "1.1.1",
|
||||
"@radix-ui/react-avatar": "1.1.2",
|
||||
"@radix-ui/react-checkbox": "1.1.3",
|
||||
"@radix-ui/react-collapsible": "1.1.2",
|
||||
"@radix-ui/react-context-menu": "2.2.4",
|
||||
"@radix-ui/react-dialog": "1.1.4",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.4",
|
||||
"@radix-ui/react-hover-card": "1.1.4",
|
||||
"@radix-ui/react-label": "2.1.1",
|
||||
"@radix-ui/react-menubar": "1.1.4",
|
||||
"@radix-ui/react-navigation-menu": "1.2.3",
|
||||
"@radix-ui/react-popover": "1.1.4",
|
||||
"@radix-ui/react-progress": "1.1.1",
|
||||
"@radix-ui/react-radio-group": "1.2.2",
|
||||
"@radix-ui/react-scroll-area": "1.2.2",
|
||||
"@radix-ui/react-select": "2.1.4",
|
||||
"@radix-ui/react-separator": "1.1.1",
|
||||
"@radix-ui/react-slider": "1.2.2",
|
||||
"@radix-ui/react-switch": "1.1.2",
|
||||
"@radix-ui/react-tabs": "1.1.2",
|
||||
"@radix-ui/react-toast": "1.2.4",
|
||||
"@radix-ui/react-toggle": "1.1.1",
|
||||
"@radix-ui/react-toggle-group": "1.1.1",
|
||||
"@radix-ui/react-tooltip": "1.1.6",
|
||||
"@vercel/analytics": "1.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cmdk": "1.0.4",
|
||||
"date-fns": "4.1.0",
|
||||
"embla-carousel-react": "8.5.1",
|
||||
"input-otp": "1.4.1",
|
||||
"next": "16.0.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"react-day-picker": "9.8.0",
|
||||
"react-hook-form": "^7.60.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"recharts": "2.15.4",
|
||||
"sonner": "^1.7.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"postcss": "^8.5",
|
||||
"tailwindcss": "^4.1.9",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
BIN
astro-website/public/093_Wallpaper_6c4644.jpg
Normal file
BIN
astro-website/public/093_Wallpaper_6c4644.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
9
astro-website/public/favicon.svg
Normal file
9
astro-website/public/favicon.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 749 B |
18
astro-website/src/components/AsciiChart.tsx
Normal file
18
astro-website/src/components/AsciiChart.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
interface AsciiChartProps {
|
||||
data: number[]
|
||||
label: string
|
||||
}
|
||||
|
||||
export function AsciiChart({ data, label }: AsciiChartProps) {
|
||||
const max = Math.max(...data)
|
||||
return (
|
||||
<div className="font-mono text-xs">
|
||||
<div className="flex items-end gap-px h-16 mb-2">
|
||||
{data.map((value, i) => (
|
||||
<div key={i} className="w-1 bg-primary/60" style={{ height: `${(value / max) * 100}%` }} />
|
||||
))}
|
||||
</div>
|
||||
<p className="text-muted-foreground">{label}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
19
astro-website/src/components/PrefixItem.tsx
Normal file
19
astro-website/src/components/PrefixItem.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import type React from "react"
|
||||
|
||||
interface PrefixItemProps {
|
||||
children: React.ReactNode
|
||||
highlight?: string
|
||||
}
|
||||
|
||||
export function PrefixItem({ children, highlight }: PrefixItemProps) {
|
||||
return (
|
||||
<div className="flex gap-3 font-mono text-sm">
|
||||
<span className="text-muted-foreground">[+]</span>
|
||||
<span>
|
||||
{highlight && <span className="text-primary font-medium">{highlight}</span>}
|
||||
{highlight && " "}
|
||||
<span className="text-muted-foreground">{children}</span>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
16
astro-website/src/components/surreal/index.ts
Normal file
16
astro-website/src/components/surreal/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export { SurrealButton } from "./surreal-button"
|
||||
export {
|
||||
SurrealCard,
|
||||
SurrealCardHeader,
|
||||
SurrealCardTitle,
|
||||
SurrealCardDescription,
|
||||
SurrealCardContent,
|
||||
SurrealCardFooter,
|
||||
} from "./surreal-card"
|
||||
export { SurrealInput } from "./surreal-input"
|
||||
export { SurrealBadge } from "./surreal-badge"
|
||||
export { SurrealAvatar } from "./surreal-avatar"
|
||||
export { SurrealToggle } from "./surreal-toggle"
|
||||
export { SurrealProgress } from "./surreal-progress"
|
||||
export { SurrealDivider } from "./surreal-divider"
|
||||
export { SurrealSkeleton } from "./surreal-skeleton"
|
||||
34
astro-website/src/components/surreal/surreal-avatar.tsx
Normal file
34
astro-website/src/components/surreal/surreal-avatar.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface SurrealAvatarProps {
|
||||
src?: string
|
||||
alt?: string
|
||||
fallback?: string
|
||||
size?: "sm" | "md" | "lg"
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function SurrealAvatar({ src, alt, fallback, size = "md", className }: SurrealAvatarProps) {
|
||||
const sizeClasses = {
|
||||
sm: "w-8 h-8 text-xs",
|
||||
md: "w-10 h-10 text-sm",
|
||||
lg: "w-14 h-14 text-base",
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative overflow-hidden rounded-full bg-secondary flex items-center justify-center",
|
||||
"ring-2 ring-primary/30 transition-all duration-200 hover:ring-primary/60",
|
||||
sizeClasses[size],
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{src ? (
|
||||
<img src={src || "/placeholder.svg"} alt={alt || "Avatar"} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<span className="font-medium text-secondary-foreground uppercase">{fallback?.slice(0, 2) || "?"}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
26
astro-website/src/components/surreal/surreal-badge.tsx
Normal file
26
astro-website/src/components/surreal/surreal-badge.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
interface SurrealBadgeProps {
|
||||
children: ReactNode
|
||||
variant?: "default" | "primary" | "secondary" | "outline"
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function SurrealBadge({ children, variant = "default", className }: SurrealBadgeProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center rounded-full px-3 py-1 text-xs font-medium transition-colors",
|
||||
{
|
||||
"bg-primary text-primary-foreground": variant === "default" || variant === "primary",
|
||||
"bg-secondary text-secondary-foreground": variant === "secondary",
|
||||
"border border-primary text-primary bg-transparent": variant === "outline",
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
41
astro-website/src/components/surreal/surreal-button.tsx
Normal file
41
astro-website/src/components/surreal/surreal-button.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import { forwardRef, type ButtonHTMLAttributes } from "react"
|
||||
|
||||
interface SurrealButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: "primary" | "secondary" | "ghost" | "outline"
|
||||
size?: "sm" | "md" | "lg"
|
||||
}
|
||||
|
||||
export const SurrealButton = forwardRef<HTMLButtonElement, SurrealButtonProps>(
|
||||
({ className, variant = "primary", size = "md", children, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center font-medium transition-all duration-300 ease-out",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
||||
"disabled:pointer-events-none disabled:opacity-50",
|
||||
"rounded-lg",
|
||||
{
|
||||
"bg-primary text-primary-foreground hover:brightness-110 hover:scale-[1.02] active:scale-[0.98] shadow-lg shadow-primary/30":
|
||||
variant === "primary",
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80 hover:scale-[1.02] active:scale-[0.98]":
|
||||
variant === "secondary",
|
||||
"bg-transparent text-foreground hover:bg-secondary/50": variant === "ghost",
|
||||
"border-2 border-primary bg-transparent text-primary hover:bg-primary/10": variant === "outline",
|
||||
},
|
||||
{
|
||||
"px-3 py-1.5 text-sm": size === "sm",
|
||||
"px-5 py-2.5 text-base": size === "md",
|
||||
"px-7 py-3.5 text-lg": size === "lg",
|
||||
},
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
},
|
||||
)
|
||||
SurrealButton.displayName = "SurrealButton"
|
||||
44
astro-website/src/components/surreal/surreal-card.tsx
Normal file
44
astro-website/src/components/surreal/surreal-card.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
interface SurrealCardProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
glow?: boolean
|
||||
}
|
||||
|
||||
export function SurrealCard({ children, className, glow = false }: SurrealCardProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative overflow-hidden rounded-xl bg-card p-6 border border-border",
|
||||
"transition-all duration-300 ease-out",
|
||||
"hover:border-primary/50 hover:translate-y-[-2px]",
|
||||
glow && "shadow-lg shadow-primary/20",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SurrealCardHeader({ children, className }: { children: ReactNode; className?: string }) {
|
||||
return <div className={cn("mb-4", className)}>{children}</div>
|
||||
}
|
||||
|
||||
export function SurrealCardTitle({ children, className }: { children: ReactNode; className?: string }) {
|
||||
return <h3 className={cn("text-xl font-semibold text-foreground", className)}>{children}</h3>
|
||||
}
|
||||
|
||||
export function SurrealCardDescription({ children, className }: { children: ReactNode; className?: string }) {
|
||||
return <p className={cn("text-sm text-muted-foreground mt-1", className)}>{children}</p>
|
||||
}
|
||||
|
||||
export function SurrealCardContent({ children, className }: { children: ReactNode; className?: string }) {
|
||||
return <div className={cn("text-foreground", className)}>{children}</div>
|
||||
}
|
||||
|
||||
export function SurrealCardFooter({ children, className }: { children: ReactNode; className?: string }) {
|
||||
return <div className={cn("mt-6 flex items-center gap-3", className)}>{children}</div>
|
||||
}
|
||||
20
astro-website/src/components/surreal/surreal-divider.tsx
Normal file
20
astro-website/src/components/surreal/surreal-divider.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface SurrealDividerProps {
|
||||
className?: string
|
||||
label?: string
|
||||
}
|
||||
|
||||
export function SurrealDivider({ className, label }: SurrealDividerProps) {
|
||||
if (label) {
|
||||
return (
|
||||
<div className={cn("flex items-center gap-4", className)}>
|
||||
<div className="flex-1 h-px bg-border" />
|
||||
<span className="text-xs text-muted-foreground uppercase tracking-wider">{label}</span>
|
||||
<div className="flex-1 h-px bg-border" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <div className={cn("h-px w-full bg-border", className)} />
|
||||
}
|
||||
36
astro-website/src/components/surreal/surreal-input.tsx
Normal file
36
astro-website/src/components/surreal/surreal-input.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import { forwardRef, type InputHTMLAttributes } from "react"
|
||||
|
||||
interface SurrealInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export const SurrealInput = forwardRef<HTMLInputElement, SurrealInputProps>(
|
||||
({ className, label, error, id, ...props }, ref) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{label && (
|
||||
<label htmlFor={id} className="text-sm font-medium text-foreground">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={cn(
|
||||
"w-full rounded-lg bg-input px-4 py-2.5 text-foreground placeholder:text-muted-foreground",
|
||||
"border border-border transition-all duration-200",
|
||||
"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",
|
||||
"hover:border-primary/50",
|
||||
error && "border-destructive focus:ring-destructive",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{error && <span className="text-sm text-destructive">{error}</span>}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
SurrealInput.displayName = "SurrealInput"
|
||||
24
astro-website/src/components/surreal/surreal-progress.tsx
Normal file
24
astro-website/src/components/surreal/surreal-progress.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface SurrealProgressProps {
|
||||
value: number
|
||||
max?: number
|
||||
className?: string
|
||||
showLabel?: boolean
|
||||
}
|
||||
|
||||
export function SurrealProgress({ value, max = 100, className, showLabel = false }: SurrealProgressProps) {
|
||||
const percentage = Math.min(Math.max((value / max) * 100, 0), 100)
|
||||
|
||||
return (
|
||||
<div className={cn("w-full", className)}>
|
||||
<div className="relative h-2 w-full overflow-hidden rounded-full bg-secondary">
|
||||
<div
|
||||
className="h-full bg-primary transition-all duration-500 ease-out rounded-full"
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
{showLabel && <span className="mt-1 text-xs text-muted-foreground">{Math.round(percentage)}%</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
22
astro-website/src/components/surreal/surreal-skeleton.tsx
Normal file
22
astro-website/src/components/surreal/surreal-skeleton.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface SurrealSkeletonProps {
|
||||
className?: string
|
||||
variant?: "text" | "circular" | "rectangular"
|
||||
}
|
||||
|
||||
export function SurrealSkeleton({ className, variant = "rectangular" }: SurrealSkeletonProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"animate-pulse bg-secondary/70",
|
||||
{
|
||||
"h-4 w-full rounded": variant === "text",
|
||||
"rounded-full": variant === "circular",
|
||||
"rounded-lg": variant === "rectangular",
|
||||
},
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
42
astro-website/src/components/surreal/surreal-toggle.tsx
Normal file
42
astro-website/src/components/surreal/surreal-toggle.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useState } from "react"
|
||||
|
||||
interface SurrealToggleProps {
|
||||
defaultChecked?: boolean
|
||||
onChange?: (checked: boolean) => void
|
||||
label?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function SurrealToggle({ defaultChecked = false, onChange, label, className }: SurrealToggleProps) {
|
||||
const [checked, setChecked] = useState(defaultChecked)
|
||||
|
||||
const handleToggle = () => {
|
||||
const newValue = !checked
|
||||
setChecked(newValue)
|
||||
onChange?.(newValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<label className={cn("inline-flex items-center gap-3 cursor-pointer", className)}>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
onClick={handleToggle}
|
||||
className={cn(
|
||||
"relative w-12 h-6 rounded-full transition-all duration-300",
|
||||
checked ? "bg-primary" : "bg-secondary",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"absolute top-1 w-4 h-4 rounded-full bg-foreground transition-all duration-300",
|
||||
checked ? "left-7" : "left-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
{label && <span className="text-sm text-foreground">{label}</span>}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
16
astro-website/src/layouts/main.astro
Normal file
16
astro-website/src/layouts/main.astro
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
const { content } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>{content.title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
6
astro-website/src/lib/utils.ts
Normal file
6
astro-website/src/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
295
astro-website/src/pages/index.astro
Normal file
295
astro-website/src/pages/index.astro
Normal file
@ -0,0 +1,295 @@
|
||||
---
|
||||
import '../styles/global.css'
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { SurrealButton } from "@/components/surreal/surreal-button"
|
||||
import { SurrealCard, SurrealCardHeader, SurrealCardTitle, SurrealCardDescription, SurrealCardContent, SurrealCardFooter, } from "@/components/surreal/surreal-card"
|
||||
import { SurrealInput } from "@/components/surreal/surreal-input"
|
||||
import { SurrealBadge } from "@/components/surreal/surreal-badge"
|
||||
import { SurrealAvatar } from "@/components/surreal/surreal-avatar"
|
||||
import { SurrealToggle } from "@/components/surreal/surreal-toggle"
|
||||
import { SurrealProgress } from "@/components/surreal/surreal-progress"
|
||||
import { SurrealDivider } from "@/components/surreal/surreal-divider"
|
||||
import { SurrealSkeleton } from "@/components/surreal/surreal-skeleton"
|
||||
import { AsciiChart } from "@/components/AsciiChart"
|
||||
import { PrefixItem } from "@/components/PrefixItem"
|
||||
|
||||
// Sample data for ASCII charts
|
||||
const chartData1 = [3, 5, 8, 4, 9, 7, 6, 8, 5, 9, 4, 7, 8, 6, 9, 5, 7, 8, 4, 6]
|
||||
const chartData2 = [2, 4, 6, 8, 7, 5, 9, 6, 4, 8, 7, 5, 3, 6, 8, 9, 7, 5, 4, 6]
|
||||
const chartData3 = [5, 7, 4, 8, 6, 9, 5, 7, 8, 4, 6, 9, 7, 5, 8, 6, 4, 7, 9, 5]
|
||||
---
|
||||
|
||||
<div class="min-h-screen font-mono">
|
||||
<nav class="border-b border-border">
|
||||
<div class="max-w-4xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||
<span class="font-bold tracking-tight">
|
||||
surreal<span class="text-primary">ui</span>
|
||||
</span>
|
||||
<div class="flex items-center gap-6 text-sm text-muted-foreground">
|
||||
<a href="#" class="hover:text-foreground transition-colors">
|
||||
GitHub <span class="text-primary">[2k]</span>
|
||||
</a>
|
||||
<a href="#" class="hover:text-foreground transition-colors">
|
||||
Docs
|
||||
</a>
|
||||
<a href="#" class="hover:text-foreground transition-colors">
|
||||
Components
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="max-w-4xl mx-auto px-6">
|
||||
<section class="py-20">
|
||||
<p class="text-xs text-muted-foreground mb-4">What's new in the latest release</p>
|
||||
<h1 class="text-2xl font-bold mb-4 text-balance">The component library built for the surreal</h1>
|
||||
<p class="text-sm text-muted-foreground max-w-md mb-8 leading-relaxed">
|
||||
Surreal UI is fully customizable, giving you control and freedom to use any color, any style, and any
|
||||
component.
|
||||
</p>
|
||||
<SurrealButton variant="outline" size="sm">
|
||||
Read docs →
|
||||
</SurrealButton>
|
||||
|
||||
<div class="mt-10 border border-border rounded-md overflow-hidden">
|
||||
<div class="flex gap-4 px-4 py-2 border-b border-border text-xs">
|
||||
<span class="text-foreground">npm</span>
|
||||
<span class="text-muted-foreground">yarn</span>
|
||||
<span class="text-muted-foreground">pnpm</span>
|
||||
<span class="text-muted-foreground">bun</span>
|
||||
</div>
|
||||
<div class="px-4 py-3 bg-card/50">
|
||||
<code class="text-sm">
|
||||
npx surreal-ui@latest <span class="text-primary">init</span>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SurrealDivider />
|
||||
|
||||
<section class="py-16">
|
||||
<p class="text-xs text-muted-foreground mb-2">What is Surreal UI?</p>
|
||||
<p class="text-sm text-muted-foreground mb-8 max-w-lg leading-relaxed">
|
||||
Surreal UI is a component library that helps you build beautiful interfaces with a unique coral and teal
|
||||
aesthetic.
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
<PrefixItem highlight="Buttons">Four variants with smooth hover transitions</PrefixItem>
|
||||
<PrefixItem highlight="Cards">Bordered containers with optional glow effect</PrefixItem>
|
||||
<PrefixItem highlight="Inputs">Form elements with floating labels and validation</PrefixItem>
|
||||
<PrefixItem highlight="Badges">Status indicators in multiple styles</PrefixItem>
|
||||
<PrefixItem highlight="Avatars">User representations with fallback initials</PrefixItem>
|
||||
<PrefixItem highlight="Toggles">Accessible switches for boolean settings</PrefixItem>
|
||||
<PrefixItem highlight="Progress">Visual feedback for loading states</PrefixItem>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SurrealDivider />
|
||||
|
||||
<section class="py-16">
|
||||
<p class="text-xs text-muted-foreground mb-2">The open source component library</p>
|
||||
<p class="text-sm text-muted-foreground mb-10 max-w-lg">
|
||||
[+] With over <span class="text-primary font-medium">2,000</span> GitHub stars,{" "}
|
||||
<span class="text-primary font-medium">50</span> contributors, and almost{" "}
|
||||
<span class="text-primary font-medium">200</span> commits, Surreal UI is used and trusted by over{" "}
|
||||
<span class="text-primary font-medium">10,000</span> developers every month.
|
||||
</p>
|
||||
<div class="grid grid-cols-3 gap-8">
|
||||
<AsciiChart data={chartData1} label="Fig 1. 2K GitHub Stars" />
|
||||
<AsciiChart data={chartData2} label="Fig 2. 50 Contributors" />
|
||||
<AsciiChart data={chartData3} label="Fig 3. 10,000 Monthly devs" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SurrealDivider />
|
||||
|
||||
<section class="py-16">
|
||||
<p class="text-xs text-muted-foreground mb-2">Buttons</p>
|
||||
<div class="border border-border rounded-md p-8 mt-4">
|
||||
<div class="flex flex-wrap gap-4 mb-8">
|
||||
<SurrealButton variant="primary">primary</SurrealButton>
|
||||
<SurrealButton variant="secondary">secondary</SurrealButton>
|
||||
<SurrealButton variant="outline">outline</SurrealButton>
|
||||
<SurrealButton variant="ghost">ghost</SurrealButton>
|
||||
</div>
|
||||
<SurrealDivider className="my-8" />
|
||||
<p class="text-xs text-muted-foreground mb-4">[+] sizes</p>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<SurrealButton size="sm">small</SurrealButton>
|
||||
<SurrealButton size="md">medium</SurrealButton>
|
||||
<SurrealButton size="lg">large</SurrealButton>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SurrealDivider />
|
||||
|
||||
<section class="py-16">
|
||||
<p class="text-xs text-muted-foreground mb-2">Cards</p>
|
||||
<div class="grid md:grid-cols-2 gap-6 mt-4">
|
||||
<SurrealCard glow>
|
||||
<SurrealCardHeader>
|
||||
<SurrealCardTitle className="lowercase font-mono text-sm">with glow</SurrealCardTitle>
|
||||
<SurrealCardDescription className="font-mono">subtle coral glow effect on hover</SurrealCardDescription>
|
||||
</SurrealCardHeader>
|
||||
<SurrealCardContent>
|
||||
<p class="text-muted-foreground text-sm font-mono leading-relaxed">
|
||||
[+] A luminous shadow that creates depth and draws attention to important content.
|
||||
</p>
|
||||
</SurrealCardContent>
|
||||
<SurrealCardFooter>
|
||||
<SurrealButton size="sm">explore</SurrealButton>
|
||||
</SurrealCardFooter>
|
||||
</SurrealCard>
|
||||
|
||||
<SurrealCard>
|
||||
<SurrealCardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
<SurrealCardTitle className="lowercase font-mono text-sm">standard</SurrealCardTitle>
|
||||
<SurrealBadge>new</SurrealBadge>
|
||||
</div>
|
||||
<SurrealCardDescription className="font-mono">clean minimal styling</SurrealCardDescription>
|
||||
</SurrealCardHeader>
|
||||
<SurrealCardContent>
|
||||
<p class="text-muted-foreground text-sm font-mono leading-relaxed">
|
||||
[+] The default card with hover interactions and smooth border transitions.
|
||||
</p>
|
||||
</SurrealCardContent>
|
||||
<SurrealCardFooter>
|
||||
<SurrealButton size="sm" variant="outline">
|
||||
action
|
||||
</SurrealButton>
|
||||
</SurrealCardFooter>
|
||||
</SurrealCard>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SurrealDivider />
|
||||
|
||||
<section class="py-16">
|
||||
<p class="text-xs text-muted-foreground mb-2">Form elements</p>
|
||||
<div class="border border-border rounded-md p-8 mt-4">
|
||||
<div class="grid md:grid-cols-2 gap-6 mb-8">
|
||||
<SurrealInput label="Email address" placeholder="you@example.com" type="email" />
|
||||
<SurrealInput label="Password" placeholder="enter password" type="password" />
|
||||
</div>
|
||||
<SurrealDivider className="my-8" />
|
||||
<p class="text-xs text-muted-foreground mb-4">[+] toggles</p>
|
||||
<div class="flex flex-wrap gap-8">
|
||||
<SurrealToggle label="notifications" defaultChecked />
|
||||
<SurrealToggle label="dark mode" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SurrealDivider />
|
||||
|
||||
<section class="py-16">
|
||||
<p class="text-xs text-muted-foreground mb-2">Badges + Avatars</p>
|
||||
<div class="grid md:grid-cols-2 gap-6 mt-4">
|
||||
<div class="border border-border rounded-md p-6">
|
||||
<p class="text-xs text-muted-foreground mb-4">[+] badge variants</p>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<SurrealBadge variant="primary">primary</SurrealBadge>
|
||||
<SurrealBadge variant="secondary">secondary</SurrealBadge>
|
||||
<SurrealBadge variant="outline">outline</SurrealBadge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-border rounded-md p-6">
|
||||
<p class="text-xs text-muted-foreground mb-4">[+] avatar sizes</p>
|
||||
<div class="flex items-center gap-4">
|
||||
<SurrealAvatar fallback="jd" size="sm" />
|
||||
<SurrealAvatar fallback="ab" size="md" />
|
||||
<SurrealAvatar fallback="xy" size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SurrealDivider />
|
||||
|
||||
<section class="py-16">
|
||||
<p class="text-xs text-muted-foreground mb-2">Loading states</p>
|
||||
<div class="grid md:grid-cols-2 gap-6 mt-4">
|
||||
<div class="border border-border rounded-md p-6">
|
||||
<p class="text-xs text-muted-foreground mb-4">[+] progress bars</p>
|
||||
<div class="space-y-4">
|
||||
<SurrealProgress value={25} showLabel />
|
||||
<SurrealProgress value={60} showLabel />
|
||||
<SurrealProgress value={90} showLabel />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-border rounded-md p-6">
|
||||
<p class="text-xs text-muted-foreground mb-4">[+] skeleton loaders</p>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<SurrealSkeleton variant="circular" className="w-10 h-10" />
|
||||
<div class="flex-1 space-y-2">
|
||||
<SurrealSkeleton variant="text" className="w-3/4" />
|
||||
<SurrealSkeleton variant="text" className="w-1/2" />
|
||||
</div>
|
||||
</div>
|
||||
<SurrealSkeleton variant="rectangular" className="h-16 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SurrealDivider />
|
||||
|
||||
<section class="py-16">
|
||||
<p class="text-xs text-muted-foreground mb-6">FAQ</p>
|
||||
<div class="space-y-2">
|
||||
{[
|
||||
"What is Surreal UI?",
|
||||
"How do I install Surreal UI?",
|
||||
"Do I need to configure Tailwind?",
|
||||
"Can I customize the colors?",
|
||||
"Is Surreal UI open source?",
|
||||
].map((question, i) => (
|
||||
<div key={i} class="flex items-center gap-2 py-2">
|
||||
<span class="w-1 h-1 rounded-full bg-muted-foreground" />
|
||||
<span class="text-sm font-medium">{question}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SurrealDivider />
|
||||
|
||||
<footer class="py-16">
|
||||
<p class="text-xs text-muted-foreground mb-2">Surreal UI will be available on npm soon</p>
|
||||
<p class="text-sm text-muted-foreground mb-6">Join the waitlist for early access.</p>
|
||||
<div class="flex gap-2 max-w-md">
|
||||
<SurrealInput placeholder="Email address" className="flex-1" />
|
||||
<SurrealButton variant="outline" size="sm">
|
||||
Subscribe
|
||||
</SurrealButton>
|
||||
</div>
|
||||
|
||||
<SurrealDivider className="my-12" />
|
||||
|
||||
<div class="flex items-center justify-between text-sm text-muted-foreground">
|
||||
<div class="flex gap-8">
|
||||
<a href="#" class="hover:text-foreground transition-colors">
|
||||
GitHub <span class="text-primary">[2K]</span>
|
||||
</a>
|
||||
<a href="#" class="hover:text-foreground transition-colors">
|
||||
Docs
|
||||
</a>
|
||||
<a href="#" class="hover:text-foreground transition-colors">
|
||||
Discord
|
||||
</a>
|
||||
<a href="#" class="hover:text-foreground transition-colors">
|
||||
X
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-muted-foreground mt-8">2025 Surreal UI • Brand</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
128
astro-website/src/styles/global.css
Normal file
128
astro-website/src/styles/global.css
Normal file
@ -0,0 +1,128 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
/* Retro Surrealist Theme - Inspired by coral sun & teal mountains */
|
||||
:root {
|
||||
--background: oklch(0.25 0.04 195);
|
||||
/* Deep teal */
|
||||
--foreground: oklch(0.95 0.01 195);
|
||||
/* Light teal-white */
|
||||
--card: oklch(0.2 0.04 195);
|
||||
/* Darker teal */
|
||||
--card-foreground: oklch(0.95 0.01 195);
|
||||
--popover: oklch(0.18 0.04 195);
|
||||
--popover-foreground: oklch(0.95 0.01 195);
|
||||
--primary: oklch(0.65 0.2 25);
|
||||
/* Coral red */
|
||||
--primary-foreground: oklch(0.15 0.03 195);
|
||||
--secondary: oklch(0.3 0.05 195);
|
||||
/* Mid teal */
|
||||
--secondary-foreground: oklch(0.95 0.01 195);
|
||||
--muted: oklch(0.22 0.03 195);
|
||||
--muted-foreground: oklch(0.65 0.03 195);
|
||||
--accent: oklch(0.6 0.18 25);
|
||||
/* Coral accent */
|
||||
--accent-foreground: oklch(0.15 0.03 195);
|
||||
--destructive: oklch(0.55 0.22 25);
|
||||
--destructive-foreground: oklch(0.95 0.01 195);
|
||||
--border: oklch(0.35 0.05 195);
|
||||
--input: oklch(0.28 0.04 195);
|
||||
--ring: oklch(0.65 0.2 25);
|
||||
--chart-1: oklch(0.65 0.2 25);
|
||||
--chart-2: oklch(0.5 0.08 195);
|
||||
--chart-3: oklch(0.4 0.06 195);
|
||||
--chart-4: oklch(0.7 0.15 30);
|
||||
--chart-5: oklch(0.55 0.12 20);
|
||||
--radius: 0.5rem;
|
||||
--sidebar: oklch(0.18 0.04 195);
|
||||
--sidebar-foreground: oklch(0.95 0.01 195);
|
||||
--sidebar-primary: oklch(0.65 0.2 25);
|
||||
--sidebar-primary-foreground: oklch(0.15 0.03 195);
|
||||
--sidebar-accent: oklch(0.3 0.05 195);
|
||||
--sidebar-accent-foreground: oklch(0.95 0.01 195);
|
||||
--sidebar-border: oklch(0.35 0.05 195);
|
||||
--sidebar-ring: oklch(0.65 0.2 25);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.15 0.04 195);
|
||||
--foreground: oklch(0.95 0.01 195);
|
||||
--card: oklch(0.12 0.04 195);
|
||||
--card-foreground: oklch(0.95 0.01 195);
|
||||
--popover: oklch(0.1 0.04 195);
|
||||
--popover-foreground: oklch(0.95 0.01 195);
|
||||
--primary: oklch(0.65 0.2 25);
|
||||
--primary-foreground: oklch(0.1 0.03 195);
|
||||
--secondary: oklch(0.25 0.05 195);
|
||||
--secondary-foreground: oklch(0.95 0.01 195);
|
||||
--muted: oklch(0.18 0.03 195);
|
||||
--muted-foreground: oklch(0.6 0.03 195);
|
||||
--accent: oklch(0.6 0.18 25);
|
||||
--accent-foreground: oklch(0.1 0.03 195);
|
||||
--destructive: oklch(0.55 0.22 25);
|
||||
--destructive-foreground: oklch(0.95 0.01 195);
|
||||
--border: oklch(0.3 0.05 195);
|
||||
--input: oklch(0.22 0.04 195);
|
||||
--ring: oklch(0.65 0.2 25);
|
||||
--sidebar: oklch(0.12 0.04 195);
|
||||
--sidebar-foreground: oklch(0.95 0.01 195);
|
||||
--sidebar-primary: oklch(0.65 0.2 25);
|
||||
--sidebar-primary-foreground: oklch(0.1 0.03 195);
|
||||
--sidebar-accent: oklch(0.25 0.05 195);
|
||||
--sidebar-accent-foreground: oklch(0.95 0.01 195);
|
||||
--sidebar-border: oklch(0.3 0.05 195);
|
||||
--sidebar-ring: oklch(0.65 0.2 25);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--font-sans: "Geist", "Geist Fallback";
|
||||
--font-mono: "Geist Mono", "Geist Mono Fallback";
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
20
astro-website/tsconfig.json
Normal file
20
astro-website/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [
|
||||
".astro/types.d.ts",
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user