feat: updated mobile responsiveness, refactored sidebar
This commit is contained in:
parent
c8d27cefb7
commit
cb71d2563f
94
App.tsx
94
App.tsx
@ -16,47 +16,75 @@ const App: React.FC = () => {
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const ctx = gsap.context(() => {
|
||||
// Hero Animation
|
||||
const tl = gsap.timeline();
|
||||
const mm = gsap.matchMedia();
|
||||
|
||||
tl.fromTo(titleRef.current,
|
||||
{ y: 50, opacity: 0, rotate: 2 },
|
||||
{ y: 0, opacity: 1, rotate: 0, duration: 1, ease: "power3.out" }
|
||||
)
|
||||
.fromTo(subtitleRef.current,
|
||||
{ y: 20, opacity: 0 },
|
||||
{ y: 0, opacity: 1, duration: 0.8, ease: "power2.out" },
|
||||
"-=0.5"
|
||||
);
|
||||
// Desktop Animations
|
||||
mm.add("(min-width: 768px)", () => {
|
||||
// Hero Animation
|
||||
const tl = gsap.timeline();
|
||||
|
||||
// Section Entry Animations
|
||||
const sections = gsap.utils.toArray<HTMLElement>('.guide-section');
|
||||
sections.forEach(section => {
|
||||
gsap.fromTo(section,
|
||||
{ opacity: 0, y: 50 },
|
||||
{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
duration: 0.8,
|
||||
ease: "power2.out",
|
||||
scrollTrigger: {
|
||||
trigger: section,
|
||||
start: "top 80%",
|
||||
toggleActions: "play none none reverse"
|
||||
}
|
||||
}
|
||||
tl.fromTo(titleRef.current,
|
||||
{ y: 50, opacity: 0, rotate: 2 },
|
||||
{ y: 0, opacity: 1, rotate: 0, duration: 1, ease: "power3.out" }
|
||||
)
|
||||
.fromTo(subtitleRef.current,
|
||||
{ y: 20, opacity: 0 },
|
||||
{ y: 0, opacity: 1, duration: 0.8, ease: "power2.out" },
|
||||
"-=0.5"
|
||||
);
|
||||
|
||||
// Section Entry Animations
|
||||
const sections = gsap.utils.toArray<HTMLElement>('.guide-section');
|
||||
sections.forEach(section => {
|
||||
gsap.fromTo(section,
|
||||
{ opacity: 0, y: 50 },
|
||||
{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
duration: 0.8,
|
||||
ease: "power2.out",
|
||||
scrollTrigger: {
|
||||
trigger: section,
|
||||
start: "top 80%",
|
||||
toggleActions: "play none none reverse"
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Mobile Animations (Simplified)
|
||||
mm.add("(max-width: 767px)", () => {
|
||||
gsap.set([titleRef.current, subtitleRef.current], { opacity: 1, y: 0, rotate: 0 });
|
||||
|
||||
const sections = gsap.utils.toArray<HTMLElement>('.guide-section');
|
||||
sections.forEach(section => {
|
||||
gsap.fromTo(section,
|
||||
{ opacity: 0 },
|
||||
{
|
||||
opacity: 1,
|
||||
duration: 0.5,
|
||||
ease: "power1.out",
|
||||
scrollTrigger: {
|
||||
trigger: section,
|
||||
start: "top 90%",
|
||||
toggleActions: "play none none reverse"
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
}, mainRef);
|
||||
|
||||
return () => ctx.revert();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-background text-foreground font-sans selection:bg-primary/20 selection:text-primary">
|
||||
<div className="flex min-h-screen bg-background text-foreground font-sans selection:bg-primary/20 selection:text-primary overflow-x-hidden">
|
||||
<Sidebar />
|
||||
|
||||
<main ref={mainRef} className="flex-1 lg:ml-72 w-full">
|
||||
<main ref={mainRef} className="flex-1 lg:ml-64 w-full transition-all duration-300">
|
||||
{/* Hero Section */}
|
||||
<div ref={heroRef} className="min-h-[80vh] flex flex-col justify-center px-8 md:px-16 lg:px-24 bg-card relative overflow-hidden">
|
||||
{/* Subtle blurred blobs */}
|
||||
@ -90,14 +118,14 @@ const App: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Content Sections */}
|
||||
<div className="px-6 md:px-16 lg:px-24 py-12 max-w-5xl mx-auto pb-32">
|
||||
<div className="px-6 md:px-16 lg:px-24 py-20 max-w-5xl mx-auto pb-32">
|
||||
{GUIDE_CONTENT.map((section, index) => (
|
||||
<section
|
||||
key={section.id}
|
||||
id={section.id}
|
||||
className="guide-section mb-24 scroll-mt-24"
|
||||
className="guide-section mb-32 scroll-mt-24"
|
||||
>
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="flex items-center gap-4 mb-10">
|
||||
<span className="flex items-center justify-center w-10 h-10 rounded-full bg-foreground text-background font-mono font-bold text-lg shadow-lg">
|
||||
{index + 1}
|
||||
</span>
|
||||
@ -111,7 +139,7 @@ const App: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{index < GUIDE_CONTENT.length - 1 && (
|
||||
<div className="h-px w-full bg-gradient-to-r from-transparent via-border to-transparent mt-16"></div>
|
||||
<div className="h-px w-full bg-gradient-to-r from-transparent via-border to-transparent mt-24"></div>
|
||||
)}
|
||||
</section>
|
||||
))}
|
||||
|
||||
@ -15,34 +15,23 @@ const Callout: React.FC<{ variant?: string; content: string }> = ({ variant = 'i
|
||||
case 'warning': return <AlertTriangle className="text-destructive" />;
|
||||
case 'tip': return <Lightbulb className="text-accent" />;
|
||||
case 'success': return <CheckCircle2 className="text-primary" />;
|
||||
default: return <Info className="text-blue-500" />;
|
||||
default: return <Info className="text-primary" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getStyles = () => {
|
||||
switch(variant) {
|
||||
case 'warning': return 'bg-destructive/10 border-destructive/20 text-destructive-foreground';
|
||||
case 'tip': return 'bg-accent/10 border-accent/20 text-accent-foreground';
|
||||
case 'success': return 'bg-primary/10 border-primary/20 text-primary-foreground';
|
||||
default: return 'bg-blue-50 border-blue-200 text-blue-900';
|
||||
case 'warning': return 'bg-destructive/10 border-destructive/20 text-foreground';
|
||||
case 'tip': return 'bg-accent/10 border-accent/20 text-foreground';
|
||||
case 'success': return 'bg-primary/10 border-primary/20 text-foreground';
|
||||
default: return 'bg-secondary border-secondary-foreground/10 text-secondary-foreground';
|
||||
}
|
||||
};
|
||||
|
||||
// Adjusting styles to match new variables more closely for text colors where simple bg/text classes might fail due to variable usage
|
||||
// The tailwind classes using vars (like text-primary) work if defined in config, which we did.
|
||||
const getContainerClass = () => {
|
||||
switch(variant) {
|
||||
case 'warning': return 'bg-red-50 border-red-200 text-red-900';
|
||||
case 'tip': return 'bg-amber-50 border-amber-200 text-amber-900';
|
||||
case 'success': return 'bg-green-50 border-green-200 text-green-900';
|
||||
default: return 'bg-blue-50 border-blue-200 text-blue-900';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`flex gap-4 p-4 my-4 rounded-lg border-l-4 ${getContainerClass()}`}>
|
||||
<div className={`flex gap-4 p-4 my-4 rounded-lg border-l-4 ${getStyles()}`}>
|
||||
<div className="shrink-0 pt-0.5">{getIcon()}</div>
|
||||
<p className="text-sm md:text-base">{content}</p>
|
||||
<p className="text-base">{content}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -68,11 +68,11 @@ export const GitWorkflowDiagram: React.FC = () => {
|
||||
<p className="text-xs text-muted-foreground mb-8">Where you edit files.</p>
|
||||
|
||||
{state === 'modified' && (
|
||||
<div ref={fileRef} className="bg-white p-3 rounded shadow-md border border-gray-200 flex items-center gap-3">
|
||||
<FileText className="text-orange-500" />
|
||||
<div ref={fileRef} className="bg-card p-3 rounded shadow-md border border-border flex items-center gap-3">
|
||||
<FileText className="text-destructive" />
|
||||
<div>
|
||||
<div className="text-sm font-bold">script.js</div>
|
||||
<div className="text-xs text-orange-600">Modified</div>
|
||||
<div className="text-base font-bold">script.js</div>
|
||||
<div className="text-sm text-destructive">Modified</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -82,7 +82,7 @@ export const GitWorkflowDiagram: React.FC = () => {
|
||||
disabled={state !== 'modified'}
|
||||
onClick={() => setState('staged')}
|
||||
className={`
|
||||
px-4 py-2 rounded-full text-xs font-bold transition-all
|
||||
px-4 py-2 rounded-full text-sm font-bold transition-all
|
||||
${state === 'modified'
|
||||
? 'bg-primary text-primary-foreground hover:bg-primary/90 hover:scale-105 shadow-md cursor-pointer'
|
||||
: 'bg-muted text-muted-foreground cursor-not-allowed opacity-50'
|
||||
@ -96,18 +96,18 @@ export const GitWorkflowDiagram: React.FC = () => {
|
||||
|
||||
{/* Staging Area Zone */}
|
||||
<div ref={stagingRef} className={`relative p-4 rounded-lg border-2 transition-colors duration-300 ${state === 'staged' ? 'border-primary bg-primary/5' : 'border-dashed border-muted'}`}>
|
||||
<div className="flex items-center gap-2 mb-4 text-sm font-semibold text-foreground/80">
|
||||
<div className="flex items-center gap-2 mb-4 text-base font-semibold text-foreground/80">
|
||||
<Layers size={18} />
|
||||
<span>Staging Area</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-8">Files ready to commit.</p>
|
||||
<p className="text-sm text-muted-foreground mb-8">Files ready to commit.</p>
|
||||
|
||||
{state === 'staged' && (
|
||||
<div ref={fileRef} className="bg-white p-3 rounded shadow-md border border-gray-200 flex items-center gap-3">
|
||||
<FileText className="text-green-500" />
|
||||
<div ref={fileRef} className="bg-card p-3 rounded shadow-md border border-border flex items-center gap-3">
|
||||
<FileText className="text-secondary" />
|
||||
<div>
|
||||
<div className="text-sm font-bold">script.js</div>
|
||||
<div className="text-xs text-green-600">Staged</div>
|
||||
<div className="text-base font-bold">script.js</div>
|
||||
<div className="text-sm text-secondary">Staged</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -117,7 +117,7 @@ export const GitWorkflowDiagram: React.FC = () => {
|
||||
disabled={state !== 'staged'}
|
||||
onClick={() => setState('committed')}
|
||||
className={`
|
||||
px-4 py-2 rounded-full text-xs font-bold transition-all
|
||||
px-4 py-2 rounded-full text-sm font-bold transition-all
|
||||
${state === 'staged'
|
||||
? 'bg-primary text-primary-foreground hover:bg-primary/90 hover:scale-105 shadow-md cursor-pointer'
|
||||
: 'bg-muted text-muted-foreground cursor-not-allowed opacity-50'
|
||||
@ -131,18 +131,18 @@ export const GitWorkflowDiagram: React.FC = () => {
|
||||
|
||||
{/* Repository Zone */}
|
||||
<div ref={repoRef} className={`relative p-4 rounded-lg border-2 transition-colors duration-300 ${state === 'committed' ? 'border-primary bg-primary/5' : 'border-dashed border-muted'}`}>
|
||||
<div className="flex items-center gap-2 mb-4 text-sm font-semibold text-foreground/80">
|
||||
<div className="flex items-center gap-2 mb-4 text-base font-semibold text-foreground/80">
|
||||
<Database size={18} />
|
||||
<span>Repository</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-8">Saved history.</p>
|
||||
<p className="text-sm text-muted-foreground mb-8">Saved history.</p>
|
||||
|
||||
{state === 'committed' && (
|
||||
<div ref={fileRef} className="bg-white p-3 rounded shadow-md border border-gray-200 flex items-center gap-3">
|
||||
<FileText className="text-blue-500" />
|
||||
<div ref={fileRef} className="bg-card p-3 rounded shadow-md border border-border flex items-center gap-3">
|
||||
<FileText className="text-primary" />
|
||||
<div>
|
||||
<div className="text-sm font-bold">script.js</div>
|
||||
<div className="text-xs text-blue-600">Committed</div>
|
||||
<div className="text-base font-bold">script.js</div>
|
||||
<div className="text-sm text-primary">Committed</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -151,7 +151,7 @@ export const GitWorkflowDiagram: React.FC = () => {
|
||||
<button
|
||||
disabled={state !== 'committed'}
|
||||
className={`
|
||||
px-4 py-2 rounded-full text-xs font-bold transition-all
|
||||
px-4 py-2 rounded-full text-sm font-bold transition-all
|
||||
${state === 'committed'
|
||||
? 'bg-secondary text-secondary-foreground cursor-default'
|
||||
: 'bg-muted text-muted-foreground cursor-not-allowed opacity-50'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { GUIDE_CONTENT } from '../constants';
|
||||
import { Leaf, Menu, X, GitBranch } from 'lucide-react';
|
||||
import { Leaf, Menu, X, GitBranch, ArrowRight } from 'lucide-react';
|
||||
|
||||
export const Sidebar: React.FC = () => {
|
||||
const [activeId, setActiveId] = useState<string>('');
|
||||
@ -36,60 +36,141 @@ export const Sidebar: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile Toggle */}
|
||||
{/* Mobile Toggle & Drawer (unchanged logic, adjusted styling for mobile-only) */}
|
||||
<div className="fixed top-4 right-4 z-50 lg:hidden">
|
||||
<button
|
||||
onClick={() => setIsMobileOpen(!isMobileOpen)}
|
||||
className="p-2 bg-card rounded-full shadow-lg border border-border text-foreground"
|
||||
className="p-3 bg-card rounded-full shadow-lg border border-border text-foreground hover:bg-muted transition-colors min-w-[44px] min-h-[44px] flex items-center justify-center"
|
||||
aria-label="Toggle Menu"
|
||||
>
|
||||
{isMobileOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Sidebar Container */}
|
||||
{isMobileOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-background/80 backdrop-blur-sm z-40 lg:hidden animate-in fade-in duration-200"
|
||||
onClick={() => setIsMobileOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Mobile Sidebar Content */}
|
||||
<nav className={`
|
||||
fixed top-0 left-0 h-full bg-card/95 backdrop-blur-md border-r border-border
|
||||
w-72 transform transition-transform duration-300 z-40
|
||||
fixed top-0 left-0 h-full bg-card border-r border-border
|
||||
w-[85vw] max-w-xs transform transition-transform duration-300 z-50 shadow-2xl lg:hidden flex flex-col
|
||||
${isMobileOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||
lg:translate-x-0
|
||||
`}>
|
||||
<div className="p-6 h-full flex flex-col">
|
||||
<div className="flex items-center gap-3 mb-10 group cursor-default">
|
||||
<div className="p-2 bg-primary/10 rounded-lg group-hover:bg-primary/20 transition-colors">
|
||||
<Leaf className="text-primary" size={24} />
|
||||
<div className="p-6 pb-2">
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<div className="p-2 bg-primary/10 rounded-lg">
|
||||
<Leaf className="text-primary" size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="font-bold text-foreground text-lg leading-tight">Git for Sylvi</h1>
|
||||
<h1 className="font-bold text-foreground text-lg leading-tight">Git Guide</h1>
|
||||
<span className="text-xs text-muted-foreground font-mono">v1.0.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto pr-2 custom-scrollbar">
|
||||
<div className="space-y-1">
|
||||
{GUIDE_CONTENT.map((section) => (
|
||||
<button
|
||||
key={section.id}
|
||||
onClick={() => scrollToSection(section.id)}
|
||||
className={`
|
||||
w-full text-left px-4 py-3 rounded-md text-sm transition-all duration-200
|
||||
flex items-center gap-3 group
|
||||
${activeId === section.id
|
||||
? 'bg-primary/10 text-primary font-semibold shadow-sm'
|
||||
: 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<span className={`w-1.5 h-1.5 rounded-full transition-colors ${activeId === section.id ? 'bg-primary' : 'bg-muted-foreground/30 group-hover:bg-muted-foreground'}`}></span>
|
||||
<span className="truncate">{section.title.split('. ')[1]}</span>
|
||||
</button>
|
||||
))}
|
||||
<div className="flex-1 overflow-y-auto px-4 custom-scrollbar">
|
||||
<ul className="space-y-1 pb-8">
|
||||
{GUIDE_CONTENT.map((section) => {
|
||||
const isActive = activeId === section.id;
|
||||
return (
|
||||
<li key={section.id}>
|
||||
<button
|
||||
onClick={() => scrollToSection(section.id)}
|
||||
className={`
|
||||
w-full text-left px-4 py-3 rounded-md text-base transition-all duration-200
|
||||
flex items-center justify-between
|
||||
${isActive
|
||||
? 'bg-primary/10 text-foreground font-bold'
|
||||
: 'text-muted-foreground font-medium active:bg-muted/50'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<span className="truncate">{section.title.split('. ')[1]}</span>
|
||||
{isActive && (
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-primary shrink-0"></span>
|
||||
)}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="p-6 border-t border-border/50 bg-card">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<GitBranch size={16} />
|
||||
<span>Happy Coding! 🌱</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* NEW: Minimal Desktop Navigation */}
|
||||
<nav className="hidden lg:block fixed top-0 left-0 h-full w-64 pl-6 pr-4" aria-label="Desktop Navigation">
|
||||
{/* Positioning container to match the 'mt-[187px]' feel, centered vertically or with specific top padding */}
|
||||
<div className="h-full flex flex-col justify-center max-h-[calc(100vh-40px)] overflow-y-auto overflow-x-hidden custom-scrollbar pb-10">
|
||||
|
||||
{/* Header/Logo Area for Desktop */}
|
||||
<div className="mb-6 px-3 group cursor-default">
|
||||
<div className="flex items-center gap-3 opacity-70 group-hover:opacity-100 transition-opacity">
|
||||
<Leaf className="text-primary" size={18} />
|
||||
<span className="font-bold text-foreground tracking-tight text-sm">Git Guide</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-border">
|
||||
<div className="flex items-center gap-3 text-sm text-muted-foreground">
|
||||
<GitBranch size={16} />
|
||||
<span>Happy Coding! 🌱</span>
|
||||
<ul className="space-y-0.5">
|
||||
{GUIDE_CONTENT.map((section) => {
|
||||
const isActive = activeId === section.id;
|
||||
return (
|
||||
<li key={section.id} className="group relative">
|
||||
{/* Hover/Active Pill Background */}
|
||||
<div
|
||||
className={`
|
||||
absolute left-0 top-0 z-[-1] h-full w-full rounded-md transition-all duration-200 ease-linear
|
||||
${isActive ? 'bg-primary/10 opacity-100' : 'bg-primary/5 opacity-0 group-hover:opacity-100'}
|
||||
`}
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={() => scrollToSection(section.id)}
|
||||
className={`
|
||||
relative block w-full text-left px-3 py-1.5 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 rounded-md
|
||||
${isActive
|
||||
? 'text-foreground font-bold'
|
||||
: 'text-muted-foreground font-medium hover:text-foreground'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<span className="flex items-center justify-between text-sm">
|
||||
<span>{section.title.split('. ')[1]}</span>
|
||||
|
||||
{/* Minimal Arrow on Hover/Active */}
|
||||
<span
|
||||
className={`
|
||||
transition-all duration-200 transform
|
||||
${isActive
|
||||
? 'opacity-100 translate-x-0 text-primary'
|
||||
: 'opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 text-muted-foreground'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ArrowRight size={12} strokeWidth={2.5} />
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
||||
{/* Footer Area */}
|
||||
<div className="mt-8 px-3 pt-4 border-t border-border/50">
|
||||
<div className="flex items-center gap-2 text-[10px] text-muted-foreground hover:text-primary transition-colors cursor-pointer group">
|
||||
<GitBranch size={12} className="group-hover:rotate-12 transition-transform" />
|
||||
<span>v1.0.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -66,7 +66,7 @@ export const TerminalBlock: React.FC<TerminalBlockProps> = ({ code, language = '
|
||||
<div
|
||||
ref={containerRef}
|
||||
// Using OKLCH based on the primary hue (142.77) for a perfectly matched "dark forest" look
|
||||
className="my-10 rounded-[2rem] overflow-hidden border border-primary/20 shadow-2xl relative group bg-[oklch(0.18_0.02_142.77)]"
|
||||
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)'
|
||||
}}
|
||||
@ -88,7 +88,7 @@ export const TerminalBlock: React.FC<TerminalBlockProps> = ({ code, language = '
|
||||
</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-[10px] font-bold uppercase tracking-[0.2em] text-primary-foreground/70">
|
||||
<span className="text-xs font-bold uppercase tracking-[0.2em] text-primary-foreground/70">
|
||||
{language}
|
||||
</span>
|
||||
</div>
|
||||
@ -109,12 +109,12 @@ export const TerminalBlock: React.FC<TerminalBlockProps> = ({ code, language = '
|
||||
{copied ? (
|
||||
<>
|
||||
<Check size={14} className="stroke-[3px]" />
|
||||
<span className="text-[10px] font-black uppercase">Copied!</span>
|
||||
<span className="text-xs font-black uppercase">Copied!</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy size={14} className="group-hover/btn:rotate-12 transition-transform" />
|
||||
<span className="text-[10px] font-black uppercase">Copy</span>
|
||||
<span className="text-xs font-black uppercase">Copy</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
191
index.css
Normal file
191
index.css
Normal file
@ -0,0 +1,191 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:wght@400;500;600&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Sora:wght@400;600;700&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: oklch(0.9818 0.0054 95.0986);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: oklch(0.8847 0.0069 97.3627);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
overflow-x: hidden;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: oklch(0.9818 0.0054 95.0986);
|
||||
--foreground: oklch(0.3438 0.0269 95.7226);
|
||||
--card: oklch(0.9818 0.0054 95.0986);
|
||||
--card-foreground: oklch(0.2174 0.0019 106.5582);
|
||||
--popover: oklch(1.0000 0 0);
|
||||
--popover-foreground: oklch(0.2671 0.0196 98.9390);
|
||||
--primary: oklch(0.6551 0.0198 142.7714);
|
||||
--primary-foreground: oklch(0.9688 0.0066 106.5223);
|
||||
--secondary: oklch(0.9245 0.0138 92.9892);
|
||||
--secondary-foreground: oklch(0.3821 0.0407 166.3004);
|
||||
--muted: oklch(0.9369 0.0124 91.5218);
|
||||
--muted-foreground: oklch(0.4866 0.0160 145.3235);
|
||||
--accent: oklch(0.8097 0.0177 145.4098);
|
||||
--accent-foreground: oklch(0.2891 0 0);
|
||||
--destructive: oklch(0.6507 0.1836 39.0189);
|
||||
--destructive-foreground: oklch(0.8429 0.1511 88.3005);
|
||||
--border: oklch(0.8847 0.0069 97.3627);
|
||||
--input: oklch(0.7621 0.0156 98.3528);
|
||||
--ring: oklch(0.6507 0.1836 39.0189);
|
||||
--chart-1: oklch(0.6768 0.0517 143.1617);
|
||||
--chart-2: oklch(0.3821 0.0407 166.3004);
|
||||
--chart-3: oklch(0.8429 0.1511 88.3005);
|
||||
--chart-4: oklch(0.6507 0.1836 39.0189);
|
||||
--chart-5: oklch(0.3972 0.0698 227.1914);
|
||||
--sidebar: oklch(0.6551 0.0198 142.7714);
|
||||
--sidebar-foreground: oklch(0.9688 0.0066 106.5223);
|
||||
--sidebar-primary: oklch(0.3821 0.0407 166.3004);
|
||||
--sidebar-primary-foreground: oklch(0.9881 0 0);
|
||||
--sidebar-accent: oklch(0.6121 0.0189 145.3393);
|
||||
--sidebar-accent-foreground: oklch(1.0000 0 0);
|
||||
--sidebar-border: oklch(0 0 0);
|
||||
--sidebar-ring: oklch(0 0 0);
|
||||
--font-sans: Sora, ui-sans-serif, sans-serif, system-ui;
|
||||
--font-serif: IBM Plex Serif, ui-serif, serif;
|
||||
--font-mono: Google Sans Code, ui-monospace, monospace;
|
||||
--radius: 0.275rem;
|
||||
--shadow-x: 0px;
|
||||
--shadow-y: 0px;
|
||||
--shadow-blur: 8px;
|
||||
--shadow-spread: -4px;
|
||||
--shadow-opacity: 0.79;
|
||||
--shadow-color: #000000;
|
||||
--shadow-2xs: 0px 0px 8px -4px hsl(0 0% 0% / 0.40);
|
||||
--shadow-xs: 0px 0px 8px -4px hsl(0 0% 0% / 0.40);
|
||||
--shadow-sm: 0px 0px 8px -4px hsl(0 0% 0% / 0.79), 0px 1px 2px -5px hsl(0 0% 0% / 0.79);
|
||||
--shadow: 0px 0px 8px -4px hsl(0 0% 0% / 0.79), 0px 1px 2px -5px hsl(0 0% 0% / 0.79);
|
||||
--shadow-md: 0px 0px 8px -4px hsl(0 0% 0% / 0.79), 0px 2px 4px -5px hsl(0 0% 0% / 0.79);
|
||||
--shadow-lg: 0px 0px 8px -4px hsl(0 0% 0% / 0.79), 0px 4px 6px -5px hsl(0 0% 0% / 0.79);
|
||||
--shadow-xl: 0px 0px 8px -4px hsl(0 0% 0% / 0.79), 0px 8px 10px -5px hsl(0 0% 0% / 0.79);
|
||||
--shadow-2xl: 0px 0px 8px -4px hsl(0 0% 0% / 1.98);
|
||||
--tracking-normal: 0em;
|
||||
--spacing: 0.25rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.2178 0 0);
|
||||
--foreground: oklch(0.8074 0.0142 93.0137);
|
||||
--card: oklch(0.2679 0.0036 106.6427);
|
||||
--card-foreground: oklch(0.9818 0.0054 95.0986);
|
||||
--popover: oklch(0.3085 0.0035 106.6039);
|
||||
--popover-foreground: oklch(0.9211 0.0040 106.4781);
|
||||
--primary: oklch(0.6551 0.0198 142.7714);
|
||||
--primary-foreground: oklch(0.9688 0.0066 106.5223);
|
||||
--secondary: oklch(0.9245 0.0138 92.9892);
|
||||
--secondary-foreground: oklch(0.3821 0.0407 166.3004);
|
||||
--muted: oklch(0.2561 0.0071 145.3653);
|
||||
--muted-foreground: oklch(0.7713 0.0169 99.0657);
|
||||
--accent: oklch(0.4890 0.0180 145.2933);
|
||||
--accent-foreground: oklch(0.9663 0.0080 98.8792);
|
||||
--destructive: oklch(0.6507 0.1836 39.0189);
|
||||
--destructive-foreground: oklch(1.0000 0 0);
|
||||
--border: oklch(0.2809 0 0);
|
||||
--input: oklch(0.6551 0.0198 142.7714);
|
||||
--ring: oklch(0.6507 0.1836 39.0189);
|
||||
--chart-1: oklch(0.6768 0.0517 143.1617);
|
||||
--chart-2: oklch(0.6979 0.0567 64.8386);
|
||||
--chart-3: oklch(0.8429 0.1511 88.3005);
|
||||
--chart-4: oklch(0.6507 0.1836 39.0189);
|
||||
--chart-5: oklch(0.3972 0.0698 227.1914);
|
||||
--sidebar: oklch(0.3740 0.0150 145.2682);
|
||||
--sidebar-foreground: oklch(0.8074 0.0142 93.0137);
|
||||
--sidebar-primary: oklch(0.3250 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.9881 0 0);
|
||||
--sidebar-accent: oklch(0.5451 0.0175 145.3296);
|
||||
--sidebar-accent-foreground: oklch(1.0000 0 0);
|
||||
--sidebar-border: oklch(0.9401 0 0);
|
||||
--sidebar-ring: oklch(0.7731 0 0);
|
||||
--font-sans: Sora, ui-sans-serif, sans-serif, system-ui;
|
||||
--font-serif: IBM Plex Serif, ui-serif, serif;
|
||||
--font-mono: Google Sans Code, ui-monospace, monospace;
|
||||
--radius: 0.275rem;
|
||||
--shadow-x: 0px;
|
||||
--shadow-y: 0px;
|
||||
--shadow-blur: 8px;
|
||||
--shadow-spread: -4px;
|
||||
--shadow-opacity: 0.79;
|
||||
--shadow-color: oklch(0 0 0);
|
||||
--shadow-2xs: 0px 0px 8px -4px hsl(0 0% 0% / 0.40);
|
||||
--shadow-xs: 0px 0px 8px -4px hsl(0 0% 0% / 0.40);
|
||||
--shadow-sm: 0px 0px 8px -4px hsl(0 0% 0% / 0.79), 0px 1px 2px -5px hsl(0 0% 0% / 0.79);
|
||||
--shadow: 0px 0px 8px -4px hsl(0 0% 0% / 0.79), 0px 1px 2px -5px hsl(0 0% 0% / 0.79);
|
||||
--shadow-md: 0px 0px 8px -4px hsl(0 0% 0% / 0.79), 0px 2px 4px -5px hsl(0 0% 0% / 0.79);
|
||||
--shadow-lg: 0px 0px 8px -4px hsl(0 0% 0% / 0.79), 0px 4px 6px -5px hsl(0 0% 0% / 0.79);
|
||||
--shadow-xl: 0px 0px 8px -4px hsl(0 0% 0% / 0.79), 0px 8px 10px -5px hsl(0 0% 0% / 0.79);
|
||||
--shadow-2xl: 0px 0px 8px -4px hsl(0 0% 0% / 1.98);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--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);
|
||||
--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);
|
||||
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-mono);
|
||||
--font-serif: var(--font-serif);
|
||||
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
|
||||
--shadow-2xs: var(--shadow-2xs);
|
||||
--shadow-xs: var(--shadow-xs);
|
||||
--shadow-sm: var(--shadow-sm);
|
||||
--shadow: var(--shadow);
|
||||
--shadow-md: var(--shadow-md);
|
||||
--shadow-lg: var(--shadow-lg);
|
||||
--shadow-xl: var(--shadow-xl);
|
||||
--shadow-2xl: var(--shadow-2xl);
|
||||
}
|
||||
83
index.html
83
index.html
@ -15,7 +15,11 @@
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['var(--font-sans)', 'Sora', 'Inter', 'sans-serif'],
|
||||
mono: ['var(--font-mono)', 'JetBrains Mono', 'monospace'],
|
||||
serif: ['var(--font-serif)', 'IBM Plex Serif', 'serif'],
|
||||
mono: ['var(--font-mono)', 'Google Sans Mono', 'JetBrains Mono', 'monospace'],
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: 'var(--radius)',
|
||||
},
|
||||
colors: {
|
||||
background: 'var(--background)',
|
||||
@ -57,82 +61,6 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: oklch(0.9818 0.0054 95.0986);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: oklch(0.8847 0.0069 97.3627);
|
||||
border-radius: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
overflow-x: hidden;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: oklch(0.9818 0.0054 95.0986);
|
||||
--foreground: oklch(0.3438 0.0269 95.7226);
|
||||
--card: oklch(0.9818 0.0054 95.0986);
|
||||
--card-foreground: oklch(0.2174 0.0019 106.5582);
|
||||
--popover: oklch(1.0000 0 0);
|
||||
--popover-foreground: oklch(0.2671 0.0196 98.9390);
|
||||
--primary: oklch(0.6551 0.0198 142.7714);
|
||||
--primary-foreground: oklch(0.9688 0.0066 106.5223);
|
||||
--secondary: oklch(0.9245 0.0138 92.9892);
|
||||
--secondary-foreground: oklch(0.3821 0.0407 166.3004);
|
||||
--muted: oklch(0.9369 0.0124 91.5218);
|
||||
--muted-foreground: oklch(0.4866 0.0160 145.3235);
|
||||
--accent: oklch(0.8097 0.0177 145.4098);
|
||||
--accent-foreground: oklch(0.2891 0 0);
|
||||
--destructive: oklch(0.6507 0.1836 39.0189);
|
||||
--destructive-foreground: oklch(0.8429 0.1511 88.3005);
|
||||
--border: oklch(0.8847 0.0069 97.3627);
|
||||
--input: oklch(0.7621 0.0156 98.3528);
|
||||
--ring: oklch(0.6507 0.1836 39.0189);
|
||||
--font-sans: 'Sora', 'Inter', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.grain-texture {
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
|
||||
opacity: 0.05;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.2178 0 0);
|
||||
--foreground: oklch(0.8074 0.0142 93.0137);
|
||||
--card: oklch(0.2679 0.0036 106.6427);
|
||||
--card-foreground: oklch(0.9818 0.0054 95.0986);
|
||||
--popover: oklch(0.3085 0.0035 106.6039);
|
||||
--popover-foreground: oklch(0.9211 0.0040 106.4781);
|
||||
--primary: oklch(0.6551 0.0198 142.7714);
|
||||
--primary-foreground: oklch(0.9688 0.0066 106.5223);
|
||||
--secondary: oklch(0.9245 0.0138 92.9892);
|
||||
--secondary-foreground: oklch(0.3821 0.0407 166.3004);
|
||||
--muted: oklch(0.2561 0.0071 145.3653);
|
||||
--muted-foreground: oklch(0.7713 0.0169 99.0657);
|
||||
--accent: oklch(0.4890 0.0180 145.2933);
|
||||
--accent-foreground: oklch(0.9663 0.0080 98.8792);
|
||||
--destructive: oklch(0.6507 0.1836 39.0189);
|
||||
--destructive-foreground: oklch(1.0000 0 0);
|
||||
--border: oklch(0.2809 0 0);
|
||||
--input: oklch(0.6551 0.0198 142.7714);
|
||||
--ring: oklch(0.6507 0.1836 39.0189);
|
||||
}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
@ -145,7 +73,6 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<link rel="stylesheet" href="/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user