(chore) init commit and add tasks

This commit is contained in:
Story Crater Bot
2026-08-18 18:33:49 -07:00
commit 6c6218ef36
61 changed files with 9851 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
'use client'
import { motion } from 'framer-motion'
interface AnimatedRolesProps {
roles: string[]
className?: string
}
export function AnimatedRoles({ roles, className }: AnimatedRolesProps) {
const animations = [
{
initial: { opacity: 0, x: -20 },
animate: { opacity: 1, x: 0 },
},
{
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
},
{
initial: { opacity: 0, x: 20 },
animate: { opacity: 1, x: 0 },
},
{
initial: { opacity: 0, scale: 0.8 },
animate: { opacity: 1, scale: 1 },
},
]
return (
<div className={className}>
{roles.map((role, index) => {
const animation = animations[index % animations.length]
return (
<motion.span
key={index}
initial={animation.initial}
animate={animation.animate}
transition={{
duration: 0.6,
delay: 0.2 + index * 0.15,
ease: 'easeOut',
}}
className="inline-block"
>
{role}
{index < roles.length - 1 && (
<motion.span
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 + index * 0.15 + 0.3 }}
className="mx-2"
>
</motion.span>
)}
</motion.span>
)
})}
</div>
)
}
+87
View File
@@ -0,0 +1,87 @@
'use client'
import { motion } from 'framer-motion'
interface AvatarWithBlobProps {
roles: string[]
}
export function AvatarWithBlob({ roles }: AvatarWithBlobProps) {
// Floating animation variants for each role blob
const getBlobVariants = (index: number) => ({
animate: {
y: [0, -20, 0],
x: [0, Math.cos((index * 2 * Math.PI) / roles.length) * 10, 0],
rotate: [0, 5, -5, 0],
transition: {
duration: 4 + index * 0.5,
repeat: Infinity,
ease: 'easeInOut',
},
},
})
return (
<div className="relative w-full max-w-md mx-auto aspect-square flex items-center justify-center">
{/* Animated background blobs */}
<div className="absolute inset-0">
{/* Center circle glow */}
<motion.div
animate={{
scale: [1, 1.1, 1],
opacity: [0.3, 0.5, 0.3],
}}
transition={{
duration: 4,
repeat: Infinity,
}}
className="absolute inset-0 bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 rounded-full blur-3xl"
/>
</div>
{/* Avatar center */}
<motion.div
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.8, delay: 0.2 }}
className="relative z-10 w-32 h-32 mx-auto"
>
<div className="w-full h-full rounded-full bg-gradient-to-br from-blue-600 to-purple-600 flex items-center justify-center text-white font-bold text-4xl border-4 border-white dark:border-gray-950 shadow-lg">
RL
</div>
</motion.div>
{/* Floating role blobs */}
{roles.map((role, index) => {
const angle = (index * 2 * Math.PI) / roles.length
const radius = 120
const x = Math.cos(angle) * radius
const y = Math.sin(angle) * radius
return (
<motion.div
key={role}
variants={getBlobVariants(index)}
animate="animate"
className="absolute z-20"
style={{
left: '50%',
top: '50%',
marginLeft: x,
marginTop: y,
}}
>
<motion.div
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.4 + index * 0.15 }}
className="px-4 py-2 rounded-full bg-white dark:bg-gray-900 border-2 border-blue-400 dark:border-blue-600 shadow-lg whitespace-nowrap text-sm font-semibold text-gray-900 dark:text-white"
>
{role}
</motion.div>
</motion.div>
)
})}
</div>
)
}
+150
View File
@@ -0,0 +1,150 @@
'use client'
import { motion } from 'framer-motion'
interface TimelineItem {
company: string
role: string
period: string
description: string
impact: string
skills: string[]
color: string
}
const timeline: TimelineItem[] = [
{
company: 'RBC',
role: 'Infrastructure Platform Architect',
period: '2020 — Present',
description: 'Leading multi-region infrastructure platform on Kubernetes. Building GitOps workflows with Terraform and Argo CD across 4 global regions.',
impact: '40% CPU reduction, 99.2% uptime',
skills: ['Kubernetes', 'Terraform', 'Argo CD', 'Go', 'AWS', 'Cilium', 'Prometheus', 'Grafana'],
color: 'from-blue-500 to-blue-600',
},
{
company: 'AWS',
role: 'Senior Software Engineer (Step Functions)',
period: '2019 — 2020',
description: 'Designed and optimized distributed orchestration platform. Built fault-tolerant task scheduling across 57+ regions serving mission-critical workloads.',
impact: '8 core components, 57+ regions',
skills: ['AWS', 'Java', 'gRPC', 'Distributed Systems', 'DynamoDB', 'CloudWatch'],
color: 'from-orange-500 to-orange-600',
},
{
company: 'Homelab',
role: 'DevOps / SRE / SDE',
period: '2021 — Present',
description: 'Production-grade bare-metal K8s cluster (Talos + Cilium). Built Temporal-based LLM inference pipeline with CPU-bound optimization and quantization.',
impact: '60% latency cut, 75% memory saved',
skills: ['LangGraph', 'Temporal', 'LLM Ops', 'PyTorch', 'Kafka', 'Observability'],
color: 'from-green-500 to-green-600',
},
{
company: 'Open Source',
role: 'Maintainer — go-flink',
period: '2022 — Present',
description: 'Building distributed DataLakeHouse framework in Go. Fault-tolerant task scheduling, streaming semantics, and efficient data processing.',
impact: 'Public repository, active development',
skills: ['Go', 'Distributed Systems', 'Data Pipeline', 'Testing', 'Architecture'],
color: 'from-purple-500 to-purple-600',
},
]
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2,
},
},
}
const itemVariants = {
hidden: { opacity: 0, x: -20 },
visible: {
opacity: 1,
x: 0,
transition: { duration: 0.6, ease: 'easeOut' },
},
}
export function ExperienceTimeline() {
return (
<section className="max-w-4xl mx-auto px-6 py-20">
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="mb-16"
>
<h2 className="text-4xl font-bold mb-4">Explore Experience</h2>
<p className="text-lg text-gray-600 dark:text-gray-400">
From AWS to RBC. Building systems at scale. Skills acquired along the journey.
</p>
</motion.div>
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="relative"
>
{/* Timeline line */}
<div className="absolute left-0 md:left-1/2 top-0 bottom-0 w-1 bg-gradient-to-b from-blue-500 via-purple-500 to-green-500 md:transform md:-translate-x-1/2" />
{/* Timeline items */}
<div className="space-y-12">
{timeline.map((item, index) => (
<motion.div
key={item.company}
variants={itemVariants}
className="relative pl-8 md:pl-0"
>
{/* Timeline dot */}
<div className={`absolute left-0 md:left-1/2 top-2 w-4 h-4 rounded-full bg-gradient-to-br ${item.color} transform md:-translate-x-1/2 -translate-x-1.5 border-4 border-white dark:border-gray-950 shadow-lg`} />
{/* Content card */}
<div className={index % 2 === 0 ? 'md:w-1/2 md:pr-8' : 'md:w-1/2 md:ml-auto md:pl-8'}>
<div className="bg-white dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-shadow">
<div className="flex items-start justify-between mb-3">
<div>
<h3 className="text-xl font-bold text-gray-900 dark:text-white">
{item.company}
</h3>
<p className="text-sm font-semibold text-blue-600 dark:text-blue-400">
{item.role}
</p>
</div>
<span className="text-xs font-mono text-gray-500 dark:text-gray-400 whitespace-nowrap ml-4">
{item.period}
</span>
</div>
<p className="text-sm text-gray-700 dark:text-gray-300 mb-3">
{item.description}
</p>
<div className="bg-gradient-to-r from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-800 rounded px-3 py-2 mb-4 text-sm font-semibold text-blue-600 dark:text-blue-400">
📊 {item.impact}
</div>
<div className="flex flex-wrap gap-2">
{item.skills.map((skill) => (
<span key={skill} className="text-xs px-3 py-1 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300">
{skill}
</span>
))}
</div>
</div>
</div>
</motion.div>
))}
</div>
</motion.div>
</section>
)
}
+54
View File
@@ -0,0 +1,54 @@
'use client'
import Link from 'next/link'
import { motion } from 'framer-motion'
import { LucideIcon } from 'lucide-react'
import { LiveIndicator } from './LiveIndicator'
interface FeatureCardProps {
icon: LucideIcon
title: string
description: string
href: string
stat?: string
status?: 'live' | 'offline' | 'pending'
}
export function FeatureCard({
icon: Icon,
title,
description,
href,
stat,
status = 'live',
}: FeatureCardProps) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
<Link href={href}>
<motion.div
whileHover={{ y: -8, boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1)' }}
className="h-full border border-gray-200 dark:border-gray-700 rounded-lg p-6 hover:shadow-lg transition-shadow cursor-pointer bg-white dark:bg-gray-900"
>
<div className="flex items-start justify-between mb-4">
<Icon className="w-8 h-8 text-blue-600 dark:text-blue-400" />
<LiveIndicator status={status} />
</div>
<h3 className="text-lg font-bold mb-2">{title}</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
{description}
</p>
{stat && (
<div className="text-xs font-mono text-blue-600 dark:text-blue-400">
{stat}
</div>
)}
</motion.div>
</Link>
</motion.div>
)
}
+65
View File
@@ -0,0 +1,65 @@
'use client'
import Link from 'next/link'
import { Menu, X, Moon, Sun } from 'lucide-react'
import { useState, useEffect } from 'react'
export default function Header() {
const [open, setOpen] = useState(false)
const [dark, setDark] = useState(false)
useEffect(() => {
const isDark = document.documentElement.classList.contains('dark')
setDark(isDark)
}, [])
const toggleDark = () => {
document.documentElement.classList.toggle('dark')
setDark(!dark)
}
return (
<header className="sticky top-0 z-50 border-b border-gray-200 dark:border-gray-800 bg-white/95 dark:bg-gray-950/95 backdrop-blur">
<div className="max-w-6xl mx-auto px-6 py-4 flex justify-between items-center">
<Link href="/" className="flex items-center gap-2">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-600 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
RL
</div>
<div>
<div className="text-sm font-bold text-gray-900 dark:text-white">Rock Liang</div>
<div className="text-xs text-gray-600 dark:text-gray-400">Software Engineer Lead</div>
</div>
</Link>
<nav className="hidden md:flex gap-8 items-center">
<Link href="/" className="text-sm hover:text-blue-600 dark:hover:text-blue-400">Home</Link>
<Link href="/cluster" className="text-sm hover:text-blue-600 dark:hover:text-blue-400">Cluster</Link>
<Link href="/infrastructure" className="text-sm hover:text-blue-600 dark:hover:text-blue-400">Infrastructure</Link>
<Link href="/kafka" className="text-sm hover:text-blue-600 dark:hover:text-blue-400">Kafka</Link>
<Link href="/cicd" className="text-sm hover:text-blue-600 dark:hover:text-blue-400">CI/CD</Link>
<Link href="/agent" className="text-sm hover:text-blue-600 dark:hover:text-blue-400">Agent</Link>
<button onClick={toggleDark} className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded">
{dark ? <Sun size={20} /> : <Moon size={20} />}
</button>
</nav>
<button
onClick={() => setOpen(!open)}
className="md:hidden p-2"
>
{open ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
{open && (
<div className="md:hidden border-t border-gray-200 dark:border-gray-800 p-4 space-y-2">
<Link href="/" className="block py-2 text-sm hover:text-blue-600">Home</Link>
<Link href="/infrastructure" className="block py-2 text-sm hover:text-blue-600">Infrastructure</Link>
<Link href="/kafka" className="block py-2 text-sm hover:text-blue-600">Kafka</Link>
<Link href="/cicd" className="block py-2 text-sm hover:text-blue-600">CI/CD</Link>
<Link href="/agent" className="block py-2 text-sm hover:text-blue-600">Agent</Link>
</div>
)}
</header>
)
}
+80
View File
@@ -0,0 +1,80 @@
'use client'
import { motion, AnimatePresence } from 'framer-motion'
import { useState, useEffect } from 'react'
import { Code, Network, Zap, Sparkles, Github } from 'lucide-react'
import { TypewriterText } from './TypewriterText'
const roles = [
{ text: 'Software Engineer Lead', icon: Code },
{ text: 'Infrastructure Architect', icon: Network },
{ text: 'Distributed Systems Builder', icon: Zap },
{ text: 'LLM Systems Optimizer', icon: Sparkles },
{ text: 'Open Source Developer', icon: Github },
]
export function HeroBlobFlow() {
const [index, setIndex] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setIndex((prev) => (prev + 1) % roles.length)
}, 2000)
return () => clearInterval(interval)
}, [])
return (
<div className="relative w-full max-w-md mx-auto aspect-square flex flex-col items-center justify-center">
{/* Background glow */}
<div className="absolute inset-0">
<motion.div
animate={{
scale: [1, 1.15, 1],
opacity: [0.25, 0.4, 0.25],
}}
transition={{
duration: 6,
repeat: Infinity,
ease: 'easeInOut',
}}
className="absolute inset-0 bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 rounded-full blur-3xl"
/>
</div>
{/* Avatar center */}
<motion.div
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.8, delay: 0.2 }}
className="relative z-10 w-32 h-32"
>
<div className="w-full h-full rounded-full bg-gradient-to-br from-blue-600 to-purple-600 flex items-center justify-center text-white font-bold text-4xl border-4 border-white dark:border-gray-950 shadow-xl">
RL
</div>
</motion.div>
{/* Typewriter text below avatar */}
<TypewriterText />
{/* Rotating role text */}
<div className="relative z-10 mt-12 px-6 w-full flex items-center justify-center">
<AnimatePresence mode="wait">
<motion.div
key={index}
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -20, scale: 0.95 }}
transition={{ duration: 0.6, ease: 'easeInOut' }}
className="absolute text-2xl font-semibold text-gray-900 dark:text-white text-center px-6 flex items-center gap-3 justify-center"
>
{(() => {
const Icon = roles[index].icon
return <Icon size={28} className="text-blue-600 dark:text-blue-400 flex-shrink-0" />
})()}
{roles[index].text}
</motion.div>
</AnimatePresence>
</div>
</div>
)
}
+148
View File
@@ -0,0 +1,148 @@
'use client'
import { motion } from 'framer-motion'
import { useEffect, useRef, useState } from 'react'
const commands: Record<string, string> = {
help: `Available commands:
ls - List services
kubectl - Cluster info
terraform - Infrastructure
argocd - Deployments
kafka - Message broker
about - About me
clear - Clear terminal`,
ls: `storage/
monitoring/
cicd/
sqs/
temporal/
iam/`,
kubectl: `Nodes: 3 (talos-cp-1, talos-worker-1, talos-worker-2)
Pods: 42 running
Uptime: 99.2%
Cluster Version: v1.36.1`,
terraform: `Modules: 18 deployed
Services: Kafka, PostgreSQL, MinIO, Grafana
Storage: Longhorn (3-replica)
Network: Cilium eBPF CNI`,
argocd: `Applications: 12
Synced: 11/12
Last Sync: 2 min ago
Health: Healthy`,
kafka: `Brokers: 3 (KRaft)
Replication Factor: 3
Min ISR: 2
Topics: 5 active
Throughput: ~1K msgs/sec`,
about: `Rock Liang - Senior Full-Stack Systems Engineer
Experience: 5+ years (AWS, RBC, Homelab)
Focus: Infrastructure × Backend × LLM Systems
Tech: Go, Java, Python, C++ | Kubernetes, Terraform, gRPC
Current: Building production homelab + LLM inference optimization
Open Source: go-flink (distributed DataLakeHouse)`,
}
export function InteractiveTerminal() {
const [input, setInput] = useState('')
const [history, setHistory] = useState<{ cmd: string; output: string }[]>([])
const [isOpen, setIsOpen] = useState(false)
const terminalRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault()
setIsOpen(!isOpen)
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [isOpen])
const handleCommand = (cmd: string) => {
const trimmed = cmd.trim().toLowerCase()
if (trimmed === 'clear') {
setHistory([])
setInput('')
return
}
const output = commands[trimmed] || `command not found: ${cmd}`
setHistory([...history, { cmd, output }])
setInput('')
setTimeout(() => {
terminalRef.current?.scrollTo(0, terminalRef.current.scrollHeight)
}, 0)
}
return (
<motion.div
className="fixed bottom-6 right-6 z-40"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
>
{!isOpen ? (
<button
onClick={() => setIsOpen(true)}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-mono text-sm shadow-lg"
>
Terminal (Cmd+K)
</button>
) : (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="bg-gray-950 border border-gray-700 rounded-lg shadow-2xl w-96 h-96 flex flex-col"
>
<div className="bg-gray-900 border-b border-gray-700 px-4 py-3 flex justify-between items-center">
<span className="text-gray-300 font-mono text-xs">ZSH_THEME="robbyrussell"</span>
<button
onClick={() => setIsOpen(false)}
className="text-gray-400 hover:text-white"
>
</button>
</div>
<div
ref={terminalRef}
className="flex-1 overflow-y-auto px-4 py-3 font-mono text-sm text-gray-200 space-y-2"
>
{history.length === 0 && (
<div className="text-gray-500">type 'help' for commands</div>
)}
{history.map((entry, i) => (
<div key={i}>
<div className="text-green-400"><span className="text-red-400"></span> % {entry.cmd}</div>
<div className="text-gray-300 whitespace-pre-wrap text-xs mt-1">
{entry.output}
</div>
</div>
))}
</div>
<div className="border-t border-gray-700 px-4 py-2">
<div className="flex items-center">
<span className="text-red-400 font-mono text-sm"></span>
<span className="text-green-400 font-mono text-sm ml-2">% </span>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter') handleCommand(input)
}}
placeholder=""
className="flex-1 bg-transparent text-green-400 font-mono text-sm outline-none ml-1"
autoFocus
/>
</div>
</div>
</motion.div>
)}
</motion.div>
)
}
+35
View File
@@ -0,0 +1,35 @@
'use client'
import { motion } from 'framer-motion'
interface LiveIndicatorProps {
status: 'live' | 'offline' | 'pending'
label?: string
}
export function LiveIndicator({ status, label }: LiveIndicatorProps) {
const colors = {
live: 'bg-green-500',
offline: 'bg-red-500',
pending: 'bg-yellow-500',
}
const labels = {
live: 'Live',
offline: 'Offline',
pending: 'Pending',
}
return (
<div className="flex items-center gap-2">
<motion.div
animate={status === 'live' ? { scale: [1, 1.3, 1] } : {}}
transition={{ duration: 2, repeat: Infinity }}
className={`w-2 h-2 rounded-full ${colors[status]}`}
/>
<span className="text-xs font-semibold text-gray-600 dark:text-gray-400">
{label || labels[status]}
</span>
</div>
)
}
+43
View File
@@ -0,0 +1,43 @@
'use client'
import { motion } from 'framer-motion'
interface ProgressiveTextProps {
texts: string[]
className?: string
}
export function ProgressiveText({ texts, className }: ProgressiveTextProps) {
const container = {
hidden: { opacity: 0 },
visible: (i = 1) => ({
opacity: 1,
transition: { staggerChildren: 0.12, delayChildren: 0.04 * i },
}),
}
const child = {
hidden: { opacity: 0, y: 10 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.5 },
},
}
return (
<motion.div
className={className}
variants={container}
initial="hidden"
animate="visible"
>
{texts.map((text, i) => (
<motion.div key={i} variants={child} className="inline">
{text}
{i < texts.length - 1 && ' '}
</motion.div>
))}
</motion.div>
)
}
+55
View File
@@ -0,0 +1,55 @@
'use client'
import { motion } from 'framer-motion'
import { useState, useEffect } from 'react'
const phrases = [
'a Senior Systems engineer',
'Rock Liang',
'a Senior Full-stack builder',
'a Senior Infrastructure architect',
'a Senior Backend SDE',
]
export function TypewriterText() {
const [currentPhrase, setCurrentPhrase] = useState(0)
const [displayText, setDisplayText] = useState('')
const [isDeleting, setIsDeleting] = useState(false)
useEffect(() => {
const phrase = phrases[currentPhrase]
const delay = isDeleting ? 50 : 100
const timer = setTimeout(() => {
if (!isDeleting) {
if (displayText.length < phrase.length) {
setDisplayText(phrase.substring(0, displayText.length + 1))
} else {
setTimeout(() => setIsDeleting(true), 1500)
}
} else {
if (displayText.length > 0) {
setDisplayText(phrase.substring(0, displayText.length - 1))
} else {
setIsDeleting(false)
setCurrentPhrase((prev) => (prev + 1) % phrases.length)
}
}
}, delay)
return () => clearTimeout(timer)
}, [displayText, isDeleting, currentPhrase])
return (
<div className="relative z-10 mt-6 h-10 flex items-center justify-center">
<span className="text-2xl font-semibold text-gray-800 dark:text-gray-200">
I'm <span className="text-blue-600 dark:text-blue-400">{displayText}</span>
<motion.span
animate={{ opacity: [1, 0] }}
transition={{ duration: 0.6, repeat: Infinity }}
className="inline-block w-0.5 h-7 ml-1 bg-blue-600 dark:bg-blue-400"
/>
</span>
</div>
)
}
+31
View File
@@ -0,0 +1,31 @@
import type { ReactNode } from 'react'
type Props = {
label: string
hint?: string
right?: ReactNode
children: ReactNode
className?: string
}
/** Instrument panel chrome: hairline frame, corner ticks, eyebrow label. */
export function Panel({ label, hint, right, children, className = '' }: Props) {
return (
<section className={`relative border border-wire bg-deck ${className}`}>
<span className="pointer-events-none absolute -left-px -top-px h-2 w-2 border-l border-t border-wire-lit" />
<span className="pointer-events-none absolute -right-px -top-px h-2 w-2 border-r border-t border-wire-lit" />
<span className="pointer-events-none absolute -bottom-px -left-px h-2 w-2 border-b border-l border-wire-lit" />
<span className="pointer-events-none absolute -bottom-px -right-px h-2 w-2 border-b border-r border-wire-lit" />
<header className="flex items-baseline justify-between gap-4 border-b border-wire px-4 py-2.5">
<div className="flex items-baseline gap-3">
<h2 className="font-data text-[11px] uppercase tracking-[0.18em] text-chalk">{label}</h2>
{hint && <p className="font-data text-[10px] text-dim">{hint}</p>}
</div>
{right}
</header>
<div className="p-4">{children}</div>
</section>
)
}
+43
View File
@@ -0,0 +1,43 @@
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
const SURFACES = [
{ code: 'B', href: '/cluster', label: 'Topology' },
{ code: 'E', href: '/cluster/delivery', label: 'Delivery' },
{ code: 'C', href: '/cluster/terminal', label: 'Terminal' },
{ code: 'D', href: '/cluster/chat', label: 'Chat' },
]
export function Rail() {
const pathname = usePathname()
return (
<nav
aria-label="Cluster surfaces"
className="flex shrink-0 gap-px overflow-x-auto border-b border-wire bg-void md:w-16 md:flex-col md:overflow-visible md:border-b-0 md:border-r"
>
{SURFACES.map((s) => {
const active = pathname === s.href
return (
<Link
key={s.href}
href={s.href}
aria-current={active ? 'page' : undefined}
className={[
'group flex flex-1 flex-row items-center gap-2 px-4 py-3 transition-colors md:flex-none md:flex-col md:gap-1 md:px-0 md:py-4',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-lamp',
active ? 'bg-deck text-lamp' : 'text-dim hover:bg-riser hover:text-chalk',
].join(' ')}
>
<span className="font-signage text-lg font-semibold leading-none">{s.code}</span>
<span className="font-data text-[9px] uppercase tracking-[0.14em] md:[writing-mode:vertical-rl]">
{s.label}
</span>
</Link>
)
})}
</nav>
)
}
+54
View File
@@ -0,0 +1,54 @@
'use client'
import { useEffect, useState } from 'react'
import { cluster, nodes } from '@/lib/clusterMock'
import { SlotMeter } from './SlotMeter'
/** Fake liveness so the layout can be judged in motion. Replaced by SSE in Phase 2. */
function useDrift() {
const [age, setAge] = useState<number>(cluster.snapshotAge)
const [slots, setSlots] = useState(3)
useEffect(() => {
const tick = setInterval(() => setAge((a) => (a > 6 ? 0.4 : +(a + 0.9).toFixed(1))), 900)
const churn = setInterval(() => setSlots(() => 1 + Math.floor(Math.random() * 5)), 3400)
return () => {
clearInterval(tick)
clearInterval(churn)
}
}, [])
return { age, slots }
}
export function Ribbon() {
const { age, slots } = useDrift()
return (
<div className="flex flex-wrap items-center gap-x-6 gap-y-2 border-b border-wire bg-void px-4 py-2.5 font-data text-[11px]">
<span className="flex items-center gap-2 text-chalk">
<span className="h-1.5 w-1.5 rounded-full bg-flux" />
{cluster.distro}
</span>
<span className="text-dim">
k8s <span className="text-chalk">{cluster.kubernetes}</span>
</span>
<span className="text-dim">
nodes <span className="text-chalk">{nodes.length}</span>
</span>
<span className="text-dim">
snapshot <span className="text-chalk">{age.toFixed(1)}s</span>
</span>
<span className="ml-auto flex items-center gap-3">
<span className="uppercase tracking-[0.16em] text-dim">seq</span>
<SlotMeter used={slots} />
<span className="tabular-nums text-chalk">{slots}/8</span>
</span>
<span className="border border-lamp/40 px-1.5 py-0.5 uppercase tracking-[0.16em] text-lamp">
placeholder data
</span>
</div>
)
}
+44
View File
@@ -0,0 +1,44 @@
'use client'
import { SEQUENCE_SLOTS, PUBLIC_SLOT_CAP } from '@/lib/clusterMock'
type Props = {
used: number
size?: 'sm' | 'lg'
showCap?: boolean
}
/**
* The signature element. Eight discrete cells, one per vLLM sequence slot.
* Cells past PUBLIC_SLOT_CAP are operator headroom and read as reserved.
*/
export function SlotMeter({ used, size = 'sm', showCap = false }: Props) {
const cell = size === 'lg' ? 'h-6 w-3' : 'h-3 w-[7px]'
return (
<div className="flex items-center gap-2">
<div className="flex gap-[3px]" role="img" aria-label={`${used} of ${SEQUENCE_SLOTS} sequence slots in use`}>
{Array.from({ length: SEQUENCE_SLOTS }, (_, i) => {
const reserved = i >= PUBLIC_SLOT_CAP
const active = i < used
return (
<span
key={i}
className={[
cell,
'rounded-[1px] transition-colors duration-500',
active ? 'bg-lamp' : reserved ? 'bg-wire' : 'bg-wire-lit/40',
reserved && !active ? 'opacity-50' : '',
].join(' ')}
/>
)
})}
</div>
{showCap && (
<span className="font-data text-[10px] uppercase tracking-widest text-dim">
{used}/{PUBLIC_SLOT_CAP} public · {SEQUENCE_SLOTS} total
</span>
)}
</div>
)
}