welcome-sylvi/components/TerminalBlock.tsx

163 lines
6.3 KiB
TypeScript

import React, { useRef, useLayoutEffect, useState } from 'react';
import { Copy, Check, Sprout, Leaf, Wind } from 'lucide-react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
interface TerminalBlockProps {
code: string;
language?: string;
}
export const TerminalBlock: React.FC<TerminalBlockProps> = ({ code, language = 'bash' }) => {
const [copied, setCopied] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const copyBtnRef = useRef<HTMLButtonElement>(null);
const handleCopy = () => {
navigator.clipboard.writeText(code);
setCopied(true);
// Organic "rustle" animation on click
if (copyBtnRef.current) {
gsap.to(copyBtnRef.current, {
rotate: 15,
scale: 1.1,
duration: 0.1,
yoyo: true,
repeat: 1,
ease: "sine.inOut"
});
}
setTimeout(() => setCopied(false), 2000);
};
useLayoutEffect(() => {
const ctx = gsap.context(() => {
// Growth animation using a "spring-like" organic entry
gsap.fromTo(containerRef.current,
{
opacity: 0,
y: 40,
scale: 0.95,
skewX: -1
},
{
opacity: 1,
y: 0,
scale: 1,
skewX: 0,
duration: 1,
ease: "elastic.out(1, 0.75)",
scrollTrigger: {
trigger: containerRef.current,
start: "top 92%",
}
}
);
}, containerRef);
return () => ctx.revert();
}, []);
return (
<div
ref={containerRef}
// Using OKLCH based on the primary hue (142.77) for a perfectly matched "dark forest" look
className="my-6 md:my-10 rounded-[2rem] overflow-hidden border border-primary/20 shadow-2xl relative group bg-[oklch(0.18_0.02_142.77)]"
style={{
boxShadow: '0 20px 50px -12px oklch(0.15 0.05 142.77 / 0.5)'
}}
>
{/* Texture Overlay from index.html */}
<div className="absolute inset-0 grain-texture opacity-10 pointer-events-none" />
{/* Nature-inspired Gradient Mask */}
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-accent/5 pointer-events-none" />
{/* Organic Header - "The Pebble" style */}
<div className="flex items-center justify-between px-8 py-4 bg-[oklch(0.25_0.03_142.77)]/80 backdrop-blur-md border-b border-white/5 relative z-10">
<div className="flex items-center space-x-4">
<div className="flex space-x-2">
{/* Organic leaf-shaded dots */}
<div className="w-2.5 h-2.5 rounded-full bg-primary animate-pulse" style={{ animationDelay: '0s' }}></div>
<div className="w-2.5 h-2.5 rounded-full bg-accent animate-pulse" style={{ animationDelay: '0.2s' }}></div>
<div className="w-2.5 h-2.5 rounded-full bg-secondary animate-pulse" style={{ animationDelay: '0.4s' }}></div>
</div>
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10">
<Sprout size={14} className="text-primary" />
<span className="text-xs font-bold uppercase tracking-[0.2em] text-primary-foreground/70">
{language}
</span>
</div>
</div>
<button
ref={copyBtnRef}
onClick={handleCopy}
className={`
relative overflow-hidden px-4 py-2 rounded-full transition-all duration-500 flex items-center gap-2 group/btn
${copied
? 'bg-primary text-primary-foreground'
: 'bg-white/5 hover:bg-white/10 text-white/50 hover:text-white'
}
`}
>
<div className="relative z-10 flex items-center gap-2">
{copied ? (
<>
<Check size={14} className="stroke-[3px]" />
<span className="text-xs font-black uppercase">Copied!</span>
</>
) : (
<>
<Copy size={14} className="group-hover/btn:rotate-12 transition-transform" />
<span className="text-xs font-black uppercase">Copy</span>
</>
)}
</div>
{/* Hover leaf effect */}
{!copied && (
<Wind
size={16}
className="absolute -right-4 top-1/2 -translate-y-1/2 opacity-0 group-hover/btn:right-2 group-hover/btn:opacity-20 transition-all duration-500 text-primary"
/>
)}
</button>
</div>
{/* Code Area */}
<div className="p-8 overflow-x-auto relative z-10 custom-scrollbar">
<pre className="leading-relaxed font-mono selection:bg-primary/40 selection:text-white">
<code className="text-[oklch(0.95_0.02_142.77)]">
{code.split('\n').map((line, i) => {
const isComment = line.trim().startsWith('#');
const isCommand = line.trim().startsWith('git');
return (
<span key={i} className="block relative group/line">
{/* Subtle line highlight */}
<div className="absolute -left-8 -right-8 top-0 bottom-0 bg-primary/5 opacity-0 group-hover/line:opacity-100 transition-opacity pointer-events-none" />
<span className={isComment ? 'text-primary/50 italic' : isCommand ? 'text-accent font-bold' : ''}>
{line}
</span>
{'\n'}
</span>
);
})}
</code>
</pre>
</div>
{/* Decorative Organic Elements */}
<div className="absolute bottom-4 right-4 opacity-5 group-hover:opacity-20 transition-all duration-1000 transform group-hover:scale-110 group-hover:rotate-6 pointer-events-none">
<Leaf size={120} className="text-primary" />
</div>
{/* "Growing" accent line at bottom */}
<div className="absolute bottom-0 left-0 h-1 bg-gradient-to-r from-transparent via-primary/30 to-transparent w-full scale-x-0 group-hover:scale-x-100 transition-transform duration-1000 origin-center" />
</div>
);
};