app: shell + scificn-ui frontend (SCI-FI theme, brightened --text-muted)
scificn-ui v0.1.0 integrated copy-paste per its docs: Button/Panel/Badge/Card/Alert in components/ui/, cn() helper in lib/utils.ts, theme tokens in app/globals.css. Deliberate deviation from upstream: --text-muted #4A4A40 -> #6B6B5C (+12pt lightness) for readability on the near-black background (default SCI-FI theme only). Landing page proves the integration; no game UI yet.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import * as React from 'react'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const alertVariants = cva(
|
||||
[
|
||||
'relative w-full',
|
||||
'border border-solid border-l-[3px]',
|
||||
'p-4',
|
||||
'font-mono',
|
||||
'rounded-none',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
STATUS: [
|
||||
'border-[var(--border)]',
|
||||
'border-l-[var(--color-green)]',
|
||||
'bg-[var(--surface)]',
|
||||
],
|
||||
WARNING: [
|
||||
'border-[var(--border)]',
|
||||
'border-l-[var(--color-amber)]',
|
||||
'bg-[var(--surface)]',
|
||||
],
|
||||
CRITICAL: [
|
||||
'border-[var(--border)]',
|
||||
'border-l-[var(--color-red)]',
|
||||
'bg-[var(--surface)]',
|
||||
],
|
||||
INFO: [
|
||||
'border-[var(--border)]',
|
||||
'border-l-[var(--color-blue)]',
|
||||
'bg-[var(--surface)]',
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultVariants: { variant: 'STATUS' },
|
||||
}
|
||||
)
|
||||
|
||||
const prefixMap = {
|
||||
STATUS: { symbol: '◈', color: 'var(--color-green)', glow: 'var(--text-glow-green)' },
|
||||
WARNING: { symbol: '⚠', color: 'var(--color-amber)', glow: 'var(--text-glow-amber)' },
|
||||
CRITICAL: { symbol: '✕', color: 'var(--color-red)', glow: 'var(--text-glow-red)' },
|
||||
INFO: { symbol: 'ℹ', color: 'var(--color-blue)', glow: 'var(--text-glow-blue)' },
|
||||
}
|
||||
|
||||
export interface AlertProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof alertVariants> {}
|
||||
|
||||
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
||||
({ className, variant = 'STATUS', children, ...props }, ref) => {
|
||||
const v = variant ?? 'STATUS'
|
||||
const prefix = prefixMap[v]
|
||||
|
||||
return (
|
||||
<div ref={ref} className={cn(alertVariants({ variant }), className)} role="alert" {...props}>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'flex-start' }}>
|
||||
<span
|
||||
style={{
|
||||
color: prefix.color,
|
||||
textShadow: prefix.glow,
|
||||
fontSize: '0.85rem',
|
||||
lineHeight: 1.5,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{prefix.symbol}
|
||||
</span>
|
||||
<div style={{ flex: 1 }}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
Alert.displayName = 'Alert'
|
||||
|
||||
/* ── AlertTitle ── */
|
||||
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||
({ className, style, ...props }, ref) => (
|
||||
<h5
|
||||
ref={ref}
|
||||
className={cn(className)}
|
||||
style={{
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 600,
|
||||
color: 'var(--text-secondary)',
|
||||
letterSpacing: '0.08em',
|
||||
textTransform: 'uppercase',
|
||||
marginBottom: '0.25rem',
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
AlertTitle.displayName = 'AlertTitle'
|
||||
|
||||
/* ── AlertDescription ── */
|
||||
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||
({ className, style, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn(className)}
|
||||
style={{
|
||||
fontSize: '0.8rem',
|
||||
color: 'var(--text-muted)',
|
||||
lineHeight: 1.6,
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
AlertDescription.displayName = 'AlertDescription'
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription, alertVariants }
|
||||
@@ -0,0 +1,83 @@
|
||||
import * as React from 'react'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const badgeVariants = cva(
|
||||
[
|
||||
'inline-flex items-center gap-1.5',
|
||||
'px-2 py-0.5',
|
||||
'text-[0.65rem] font-mono font-medium uppercase tracking-widest',
|
||||
'border border-solid',
|
||||
'rounded-none',
|
||||
'whitespace-nowrap',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
ACTIVE: [
|
||||
'border-[var(--color-green)]',
|
||||
'text-[var(--color-green)]',
|
||||
],
|
||||
OFFLINE: [
|
||||
'border-[var(--border)]',
|
||||
'text-[var(--text-muted)]',
|
||||
],
|
||||
WARNING: [
|
||||
'border-[var(--color-amber)]',
|
||||
'text-[var(--color-amber)]',
|
||||
],
|
||||
CRITICAL: [
|
||||
'border-[var(--color-red)]',
|
||||
'text-[var(--color-red)]',
|
||||
],
|
||||
SCANNING: [
|
||||
'border-[var(--color-green)]',
|
||||
'text-[var(--color-green)]',
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'ACTIVE',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLSpanElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, children, ...props }: BadgeProps) {
|
||||
const isScanning = variant === 'SCANNING'
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
style={
|
||||
variant === 'ACTIVE'
|
||||
? { textShadow: 'var(--text-glow-green)', boxShadow: 'inset 0 0 8px #00ed3f11' }
|
||||
: variant === 'CRITICAL'
|
||||
? { textShadow: 'var(--text-glow-red)', boxShadow: 'inset 0 0 8px #cc220011' }
|
||||
: undefined
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{isScanning && (
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
animation: 'blink-cursor 1s step-end infinite',
|
||||
}}
|
||||
>
|
||||
●
|
||||
</span>
|
||||
)}
|
||||
{!isScanning && variant === 'ACTIVE' && <span>●</span>}
|
||||
{!isScanning && variant === 'OFFLINE' && <span>○</span>}
|
||||
{!isScanning && variant === 'WARNING' && <span>⚠</span>}
|
||||
{!isScanning && variant === 'CRITICAL' && <span>✕</span>}
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
@@ -0,0 +1,91 @@
|
||||
import * as React from 'react'
|
||||
import { Slot } from '@radix-ui/react-slot'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const buttonVariants = cva(
|
||||
[
|
||||
'inline-flex items-center justify-center',
|
||||
'font-mono font-medium uppercase tracking-widest',
|
||||
'border border-solid',
|
||||
'cursor-pointer select-none',
|
||||
'transition-all duration-150',
|
||||
'focus-visible:outline-none',
|
||||
'disabled:opacity-40 disabled:cursor-not-allowed disabled:pointer-events-none',
|
||||
'rounded-none',
|
||||
'whitespace-nowrap',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
EXEC: [
|
||||
'bg-transparent',
|
||||
'border-[var(--color-green)]',
|
||||
'text-[var(--color-green)]',
|
||||
'hover:bg-[var(--color-green)]',
|
||||
'hover:text-[var(--color-bg)]',
|
||||
'hover:shadow-[var(--glow-green)]',
|
||||
'focus-visible:shadow-[var(--glow-green)]',
|
||||
],
|
||||
GHOST: [
|
||||
'bg-transparent',
|
||||
'border-transparent',
|
||||
'text-[var(--text-secondary)]',
|
||||
'hover:text-[var(--color-green)]',
|
||||
'hover:border-[var(--border)]',
|
||||
'focus-visible:border-[var(--border)]',
|
||||
],
|
||||
OUTLINE: [
|
||||
'bg-[var(--surface)]',
|
||||
'border-[var(--border)]',
|
||||
'text-[var(--text-secondary)]',
|
||||
'hover:border-[var(--border-active)]',
|
||||
'hover:text-[var(--color-green)]',
|
||||
'hover:shadow-[var(--glow-green)]',
|
||||
'focus-visible:border-[var(--border-active)]',
|
||||
'focus-visible:shadow-[var(--glow-green)]',
|
||||
],
|
||||
ABORT: [
|
||||
'bg-transparent',
|
||||
'border-[var(--color-red)]',
|
||||
'text-[var(--color-red)]',
|
||||
'hover:bg-[var(--color-red)]',
|
||||
'hover:text-[var(--color-bg)]',
|
||||
'hover:shadow-[var(--glow-red)]',
|
||||
'focus-visible:shadow-[var(--glow-red)]',
|
||||
],
|
||||
},
|
||||
size: {
|
||||
SM: 'h-7 px-3 text-[0.65rem] gap-1.5',
|
||||
MD: 'h-9 px-4 text-[0.75rem] gap-2',
|
||||
LG: 'h-11 px-6 text-[0.875rem] gap-2.5',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'OUTLINE',
|
||||
size: 'MD',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : 'button'
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn(buttonVariants({ variant, size }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = 'Button'
|
||||
|
||||
export { Button, buttonVariants }
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'bg-[var(--surface)] border border-[var(--border)]',
|
||||
'text-[var(--text-secondary)] font-mono',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
Card.displayName = 'Card'
|
||||
|
||||
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex flex-col gap-1.5 p-4 border-b border-[var(--border)]', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
CardHeader.displayName = 'CardHeader'
|
||||
|
||||
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-sm font-semibold uppercase tracking-widest text-[var(--color-green)]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
CardTitle.displayName = 'CardTitle'
|
||||
|
||||
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn('text-xs text-[var(--text-muted)]', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
CardDescription.displayName = 'CardDescription'
|
||||
|
||||
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('p-4', className)} {...props} />
|
||||
)
|
||||
)
|
||||
CardContent.displayName = 'CardContent'
|
||||
|
||||
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex items-center p-4 border-t border-[var(--border)]', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
CardFooter.displayName = 'CardFooter'
|
||||
|
||||
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter }
|
||||
@@ -0,0 +1,112 @@
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
/* ── Panel (outer container) ── */
|
||||
export interface PanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
notch?: 'sm' | 'md' | 'lg' | 'none'
|
||||
}
|
||||
|
||||
const Panel = React.forwardRef<HTMLDivElement, PanelProps>(
|
||||
({ className, notch = 'md', style, ...props }, ref) => {
|
||||
const clipMap = {
|
||||
sm: 'var(--clip-corner-sm)',
|
||||
md: 'var(--clip-corner-md)',
|
||||
lg: 'var(--clip-corner-lg)',
|
||||
none: 'none',
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('rounded-none', className)}
|
||||
style={{
|
||||
background: 'var(--surface)',
|
||||
border: '1px solid var(--border)',
|
||||
clipPath: clipMap[notch],
|
||||
overflow: 'hidden',
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Panel.displayName = 'Panel'
|
||||
|
||||
/* ── PanelHeader ── */
|
||||
const PanelHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, style, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(className)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0.6rem 1rem',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
background: 'var(--surface-raised)',
|
||||
gap: '0.5rem',
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
PanelHeader.displayName = 'PanelHeader'
|
||||
|
||||
/* ── PanelTitle ── */
|
||||
const PanelTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||
({ className, style, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn(className)}
|
||||
style={{
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 600,
|
||||
color: 'var(--text-muted)',
|
||||
letterSpacing: '0.12em',
|
||||
textTransform: 'uppercase',
|
||||
margin: 0,
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
PanelTitle.displayName = 'PanelTitle'
|
||||
|
||||
/* ── PanelContent ── */
|
||||
const PanelContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, style, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(className)}
|
||||
style={{ padding: '1rem', ...style }}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
PanelContent.displayName = 'PanelContent'
|
||||
|
||||
/* ── PanelFooter ── */
|
||||
const PanelFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, style, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(className)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0.6rem 1rem',
|
||||
borderTop: '1px solid var(--border)',
|
||||
background: 'var(--surface-raised)',
|
||||
gap: '0.5rem',
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
PanelFooter.displayName = 'PanelFooter'
|
||||
|
||||
export { Panel, PanelHeader, PanelTitle, PanelContent, PanelFooter }
|
||||
Reference in New Issue
Block a user