mirror of
https://github.com/Snigdha-OS/Snigdha-OS.github.io.git
synced 2025-09-12 15:45:02 +02:00
🚀 feat(_route): add dev to origin
This commit is contained in:
191
src/components/layout/Footer.tsx
Normal file
191
src/components/layout/Footer.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Github } from 'lucide-react'; // Importing the 'Github' icon from the 'lucide-react' library
|
||||
|
||||
// Define and export the Footer functional component
|
||||
export function Footer() {
|
||||
// Declare a state variable `githubFollowers` to store the number of GitHub followers.
|
||||
// The initial value is set to `null`. The `setGithubFollowers` function is used to update this value.
|
||||
const [githubFollowers, setGithubFollowers] = useState<number | null>(null);
|
||||
|
||||
// useEffect hook is used to run side effects. Here, it runs the effect to fetch GitHub follower count.
|
||||
useEffect(() => {
|
||||
// Define an asynchronous function to fetch data from the GitHub API.
|
||||
async function fetchGithubFollowers() {
|
||||
// Make a GET request to GitHub API for the user 'Snigdha-OS'.
|
||||
const response = await fetch('https://api.github.com/users/Snigdha-OS');
|
||||
|
||||
// Parse the response JSON to extract the data.
|
||||
const data = await response.json();
|
||||
|
||||
// Check if the `followers` property exists in the fetched data.
|
||||
if (data.followers !== undefined) {
|
||||
// Update the state with the number of followers.
|
||||
setGithubFollowers(data.followers);
|
||||
}
|
||||
}
|
||||
|
||||
// Call the `fetchGithubFollowers` function to fetch the data.
|
||||
fetchGithubFollowers();
|
||||
}, []); // Empty dependency array means this effect runs only once after the component mounts.
|
||||
|
||||
|
||||
return (
|
||||
<footer className="relative bg-[#1a202c] text-white py-16 overflow-hidden">
|
||||
{/* Background Bubble Animation */}
|
||||
<div className="absolute inset-0 pointer-events-none z-0">
|
||||
<div className="bubble-container"></div>
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto px-6 relative z-10">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-10">
|
||||
{/* Snigdha OS Section */}
|
||||
<div>
|
||||
<h3 className="font-bold text-xl mb-6 text-[#6495ED]">Snigdha OS</h3>
|
||||
<p className="text-sm text-gray-400 leading-relaxed">
|
||||
Arch-based Linux Distribution for Penetration Testing and Ethical Hacking! Experience power and simplicity
|
||||
like never before.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div>
|
||||
<h3 className="font-bold text-xl mb-6 text-[#6495ED]">Quick Links</h3>
|
||||
<ul className="space-y-4 text-sm">
|
||||
<li>
|
||||
<a href="https://blog.snigdhaos.org/" className="hover:text-[#6495ED] transition-colors">
|
||||
Blog <span className="text-xs text-gray-400">(Upcoming!)</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://forum.snigdhaos.org/" className="hover:text-[#6495ED] transition-colors">
|
||||
Forums <span className="text-xs text-gray-400">(Maintenance!)</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="hover:text-[#6495ED] transition-colors">
|
||||
Community <span className="text-xs text-gray-400">(Upcoming!)</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Documentation */}
|
||||
<div>
|
||||
<h3 className="font-bold text-xl mb-6 text-[#6495ED]">Documentation</h3>
|
||||
<ul className="space-y-4 text-sm">
|
||||
<li>
|
||||
<a
|
||||
href="https://snigdha-os.github.io/documentation/category/installation"
|
||||
className="hover:text-[#6495ED] transition-colors"
|
||||
>
|
||||
Installation Guide
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://snigdha-os.github.io/documentation/category/user-guide"
|
||||
className="hover:text-[#6495ED] transition-colors"
|
||||
>
|
||||
User Guide
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://snigdha-os.github.io/documentation/introduction/release_notes"
|
||||
className="hover:text-[#6495ED] transition-colors"
|
||||
>
|
||||
Release Notes
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Connect Section */}
|
||||
<div>
|
||||
<h3 className="font-bold text-xl mb-6 text-[#6495ED]">Connect</h3>
|
||||
<div className="flex items-center space-x-5">
|
||||
<a
|
||||
href="https://github.com/Snigdha-OS"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center space-x-3 bg-gray-800 hover:bg-[#6495ED] transition-colors px-4 py-3 rounded-lg shadow-lg"
|
||||
>
|
||||
<Github className="h-7 w-7 text-white" />
|
||||
{githubFollowers !== null && (
|
||||
<div className="text-white">
|
||||
<span className="text-sm">Followers</span>
|
||||
<span className="block text-lg font-bold">{githubFollowers}</span>
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Logo */}
|
||||
{/* <div className="flex justify-center mt-8">
|
||||
<img
|
||||
src="/snigdhaos-logo.svg" // Replace with your actual logo path
|
||||
alt="Snigdha OS Logo"
|
||||
className="h-16 w-auto"
|
||||
/>
|
||||
</div> */}
|
||||
|
||||
{/* Footer Bottom */}
|
||||
<div className="mt-16 pt-8 border-t border-gray-700 text-center text-sm text-gray-400">
|
||||
<p>
|
||||
© {new Date().getFullYear()} <span className="text-[#6495ED]">Snigdha OS</span>. Powered by <span className="font-bold text-[#6495ED]">Tonmoy Infrastructure™. </span>
|
||||
All rights reserved.
|
||||
</p>
|
||||
<p className="mt-2">Built with ❤️ by the Snigdha OS team.</p>
|
||||
{/* <p className="mt-4 text-xs text-gray-500">
|
||||
Powered by <span className="font-bold text-[#6495ED]">Tonmoy Infrastructure™</span>
|
||||
</p> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Inline Styles for Bubble Animation */}
|
||||
<style>{`
|
||||
.bubble-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Creating multiple bubbles */
|
||||
.bubble {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(100, 149, 237, 0.7);
|
||||
animation: bubble-move 6s infinite;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.bubble:nth-child(1) { width: 50px; height: 50px; animation-duration: 7s; top: 80%; left: 30%; }
|
||||
.bubble:nth-child(2) { width: 60px; height: 60px; animation-duration: 5s; top: 70%; left: 40%; }
|
||||
.bubble:nth-child(3) { width: 40px; height: 40px; animation-duration: 6s; top: 80%; left: 50%; }
|
||||
.bubble:nth-child(4) { width: 70px; height: 70px; animation-duration: 8s; top: 60%; left: 20%; }
|
||||
.bubble:nth-child(5) { width: 80px; height: 80px; animation-duration: 10s; top: 90%; left: 60%; }
|
||||
.bubble:nth-child(6) { width: 50px; height: 50px; animation-duration: 6s; top: 50%; left: 75%; }
|
||||
|
||||
@keyframes bubble-move {
|
||||
0% {
|
||||
transform: translateX(0) translateY(0);
|
||||
opacity: 0.7;
|
||||
}
|
||||
50% {
|
||||
transform: translateX(150px) translateY(-300px);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0) translateY(-500px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</footer>
|
||||
);
|
||||
}
|
138
src/components/layout/Header.tsx
Normal file
138
src/components/layout/Header.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import React from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { Menu, X } from 'lucide-react';
|
||||
|
||||
export function Header() {
|
||||
// Use location hook to get the current route path for active link checking
|
||||
const location = useLocation();
|
||||
|
||||
// State to toggle the mobile menu
|
||||
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
||||
|
||||
// Function to check if the current path is active for styling the active link
|
||||
const isActive = (path: string) => location.pathname === path;
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-900 text-white shadow-md">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="flex items-center justify-between h-20">
|
||||
{/* Logo and brand name */}
|
||||
<Link to="/" className="flex items-center space-x-4 group">
|
||||
<img
|
||||
src="/snigdhaos-logo.svg"
|
||||
alt="Snigdha OS"
|
||||
className="h-12 w-12 group-hover:scale-125 transition-transform duration-300"
|
||||
/>
|
||||
<span className="font-extrabold text-3xl tracking-wide text-[#6495ED] group-hover:text-white transition-colors duration-300">
|
||||
SNIGDHA OS
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Mobile menu toggle button */}
|
||||
<button
|
||||
className="md:hidden p-2 rounded-lg hover:bg-gray-800 transition-all focus:outline-none focus:ring-2 focus:ring-[#6495ED]"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)} // Toggle menu open/close state
|
||||
>
|
||||
{isMenuOpen ? (
|
||||
// If menu is open, show 'X' icon
|
||||
<X className="h-7 w-7 animate-spin-reverse" />
|
||||
) : (
|
||||
// If menu is closed, show 'Menu' icon
|
||||
<Menu className="h-7 w-7 animate-spin" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Desktop navigation links (hidden on mobile) */}
|
||||
<nav className="hidden md:flex space-x-10">
|
||||
{/* Render navigation links */}
|
||||
<NavLinks isActive={isActive} closeMenu={() => setIsMenuOpen(false)} animate />
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Mobile navigation */}
|
||||
<div
|
||||
className={`${
|
||||
// Apply fade-in or fade-out animation based on the menu state
|
||||
isMenuOpen ? 'animate-fade-in-down' : 'animate-fade-out-up'
|
||||
} md:hidden overflow-hidden transition-all duration-500`}
|
||||
>
|
||||
{/* Only render the mobile menu if it is open */}
|
||||
{isMenuOpen && (
|
||||
<nav className="mt-4">
|
||||
{/* Mobile menu styling */}
|
||||
<div className="flex flex-col space-y-4 bg-gray-800 p-5 rounded-lg shadow-lg">
|
||||
{/* Render navigation links in the mobile menu */}
|
||||
<NavLinks
|
||||
isActive={isActive}
|
||||
closeMenu={() => setIsMenuOpen(false)}
|
||||
animate
|
||||
/>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Global Keyframe Animations (inline style tag) */}
|
||||
{/* These animations control the fade-in and fade-out effects for menu items */}
|
||||
<style>{`
|
||||
@keyframes fade-in-down {
|
||||
0% { opacity: 0; transform: translateY(-10px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes fade-out-up {
|
||||
0% { opacity: 1; transform: translateY(0); }
|
||||
100% { opacity: 0; transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
@keyframes fade-in-up {
|
||||
0% { opacity: 0; transform: translateY(10px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
`}</style>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
// Navigation links component
|
||||
function NavLinks({
|
||||
isActive, // Function to check if a link is active
|
||||
closeMenu, // Function to close the mobile menu after selecting a link
|
||||
animate, // Whether to apply animation for the links
|
||||
}: {
|
||||
isActive: (path: string) => boolean;
|
||||
closeMenu: () => void;
|
||||
animate?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<ul className="space-y-4 md:space-y-0 md:flex md:space-x-10">
|
||||
{/* Loop through paths and labels to create navigation links */}
|
||||
{['/', '/about', '/download', '/donors', '/maintainers'].map((path, idx) => {
|
||||
const labels = ['Home', 'About', 'Download', 'Donors', 'Maintainers'];
|
||||
return (
|
||||
<li
|
||||
key={path}
|
||||
className={`${
|
||||
// Add animation delay if `animate` is true
|
||||
animate ? `animate-fade-in-up delay-${idx * 100}` : ''
|
||||
}`}
|
||||
>
|
||||
<Link
|
||||
to={path}
|
||||
className={`${
|
||||
// If the link is active, apply custom active styles
|
||||
isActive(path)
|
||||
? 'text-[#6495ED] underline underline-offset-4 decoration-2'
|
||||
: 'text-gray-300 hover:text-[#6495ED]'
|
||||
} font-medium transition-all duration-300 hover:scale-110`}
|
||||
onClick={closeMenu} // Close the mobile menu when a link is clicked
|
||||
>
|
||||
{labels[idx]} {/* Display label for the link */}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
26
src/components/layout/Layout.tsx
Normal file
26
src/components/layout/Layout.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react'; // Import the React library for creating React components.
|
||||
import { Header } from './Header'; // Import the `Header` component from the specified file.
|
||||
import { Footer } from './Footer'; // Import the `Footer` component from the specified file.
|
||||
|
||||
// Define the props type for the `Layout` component.
|
||||
// `children` is a special React prop that represents the content passed between the opening and closing tags of the component.
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode; // `children` can be any valid React node (elements, strings, numbers, fragments, etc.).
|
||||
}
|
||||
|
||||
// Define and export the `Layout` functional component, which accepts `LayoutProps` as props.
|
||||
export function Layout({ children }: LayoutProps) {
|
||||
// Render the component structure.
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
{/* The main container is styled as a flexbox with a column direction and a minimum height to fill the screen. */}
|
||||
<Header /> {/* Render the `Header` component at the top of the layout. */}
|
||||
<main className="flex-grow">
|
||||
{/* Render the `children` prop inside the `main` section.
|
||||
`flex-grow` allows this section to expand and take up the remaining available space. */}
|
||||
{children}
|
||||
</main>
|
||||
<Footer /> {/* Render the `Footer` component at the bottom of the layout. */}
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user