id
23
absolete_frontend/eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
|
||||
export default tseslint.config([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
14
absolete_frontend/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="reset.css">
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
4267
absolete_frontend/package-lock.json
generated
Normal file
36
absolete_frontend/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"axios": "^1.13.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router-dom": "^7.8.1",
|
||||
"tailwindcss": "^4.1.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@types/axios": "^0.9.36",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.3.0",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"vite": "^7.1.2"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 823 B |
|
Before Width: | Height: | Size: 705 B After Width: | Height: | Size: 705 B |
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 366 B |
|
Before Width: | Height: | Size: 722 B After Width: | Height: | Size: 722 B |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
@@ -15,11 +15,12 @@ interface GlobalContextType {
|
||||
setUser: React.Dispatch<React.SetStateAction<User | null>>;
|
||||
}
|
||||
|
||||
// vytvoříme a exportneme kontext
|
||||
// vytvoříme a exportneme kontext !!!
|
||||
export const UserContext = createContext<GlobalContextType | null>(null);
|
||||
|
||||
|
||||
// hook pro použití kontextu
|
||||
// zabal routy do téhle komponenty!!!
|
||||
export const UserContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
|
||||
27
absolete_frontend/tsconfig.app.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
absolete_frontend/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
25
absolete_frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
11
absolete_frontend/vite.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss()
|
||||
],
|
||||
})
|
||||
46
frontend/App.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import '@radix-ui/themes/styles.css';
|
||||
import { Theme } from '@radix-ui/themes';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
|
||||
import Home from './src/pages/Home';
|
||||
import Portfolio from './src/pages/Portfolio';
|
||||
import Services from './src/pages/Services';
|
||||
import About from './src/pages/About';
|
||||
import Blog from './src/pages/Blog';
|
||||
import Contact from './src/pages/Contact';
|
||||
import NotFound from './src/pages/NotFound';
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<Theme appearance="inherit" radius="large" scaling="100%">
|
||||
<Router>
|
||||
<main className="min-h-screen font-inter">
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/portfolio" element={<Portfolio />} />
|
||||
<Route path="/portfolio/*" element={<Portfolio />} />
|
||||
<Route path="/services" element={<Services />} />
|
||||
<Route path="/services/*" element={<Services />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/about/*" element={<About />} />
|
||||
<Route path="/blog" element={<Blog />} />
|
||||
<Route path="/contact" element={<Contact />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
<ToastContainer
|
||||
position="top-right"
|
||||
autoClose={3000}
|
||||
newestOnTop
|
||||
closeOnClick
|
||||
pauseOnHover
|
||||
/>
|
||||
</main>
|
||||
</Router>
|
||||
</Theme>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,11 +1,11 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
import globals from 'globals'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
|
||||
export default tseslint.config([
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
@@ -15,9 +15,12 @@ export default tseslint.config([
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
])
|
||||
@@ -1,14 +1,26 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="reset.css">
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/x-icon" href="https://meku.dev/favicon.ico" />
|
||||
<title>Modern Portfolio</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
|
||||
<meta name="generator" content="Meku" />
|
||||
<meta name="description" content="Modern Portfolio Generated with Meku" />
|
||||
<meta name="author" content="Meku" />
|
||||
|
||||
<meta property="og:title" content="Modern Portfolio" />
|
||||
<meta property="og:description" content="Modern Portfolio Generated with Meku" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://meku.dev/images/meku.png" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@meku_dev" />
|
||||
<meta name="twitter:image" content="https://meku.dev/images/meku.png" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script type="module" src="/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
50
frontend/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
import './styles.css';
|
||||
|
||||
// Send logs to parent frame (like a preview system)
|
||||
function postToParent(level: string, ...args: any[]): void {
|
||||
if (window.parent !== window) {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'iframe-console',
|
||||
level,
|
||||
args,
|
||||
},
|
||||
'*'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Global error handler
|
||||
window.onerror = function (message, source, lineno, colno, error) {
|
||||
const errPayload = {
|
||||
message,
|
||||
source,
|
||||
lineno,
|
||||
colno,
|
||||
stack: error?.stack,
|
||||
};
|
||||
postToParent('error', '[Meku_Error_Caught]', errPayload);
|
||||
};
|
||||
|
||||
// Unhandled promise rejection
|
||||
window.onunhandledrejection = function (event) {
|
||||
postToParent('error', '[Meku_Error_Caught]', { reason: event.reason });
|
||||
};
|
||||
|
||||
// Patch console
|
||||
(['log', 'warn', 'info', 'error'] as const).forEach((level) => {
|
||||
const original = console[level];
|
||||
console[level] = (...args: any[]) => {
|
||||
postToParent(level, ...args);
|
||||
original(...args);
|
||||
};
|
||||
});
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
5139
frontend/package-lock.json
generated
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"name": "modern-portfolio",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
@@ -10,27 +10,35 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"axios": "^1.13.0",
|
||||
"globals": "^16.4.0",
|
||||
"@hookform/resolvers": "^5.2.1",
|
||||
"@radix-ui/themes": "^3.2.1",
|
||||
"@vitejs/plugin-react": "^5.0.2",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"date-fns": "^3.6.0",
|
||||
"framer-motion": "^12.12.2",
|
||||
"lucide-react": "^0.462.0",
|
||||
"postcss": "^8.4.38",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router-dom": "^7.8.1",
|
||||
"tailwindcss": "^4.1.16"
|
||||
"react-hook-form": "^7.53.0",
|
||||
"react-resizable-panels": "^2.1.3",
|
||||
"react-router-dom": "^6.23.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"recharts": "^2.12.7",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vite": "^7.1.6",
|
||||
"zod": "^3.23.8",
|
||||
"@supabase/supabase-js": "^2.57.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@types/axios": "^0.9.36",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"eslint": "^9.33.0",
|
||||
"@eslint/js": "^9.35.0",
|
||||
"@types/react": "^19.1.13",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.3.0",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"vite": "^7.1.2"
|
||||
"typescript-eslint": "^8.43.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
6
frontend/postcss.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
14
frontend/public/robots.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Allow: /
|
||||
|
||||
User-agent: Twitterbot
|
||||
Allow: /
|
||||
|
||||
User-agent: facebookexternalhit
|
||||
Allow: /
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
149
frontend/src/components/DonateShop.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Heart, ShoppingCart, Star } from 'lucide-react';
|
||||
|
||||
const DonateShop: React.FC = () => {
|
||||
const [donatedItems, setDonatedItems] = useState<Set<number>>(new Set());
|
||||
|
||||
const products = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Coffee Support',
|
||||
price: 5,
|
||||
originalPrice: 10,
|
||||
image: 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
|
||||
description: 'Fuel my coding sessions with a virtual coffee',
|
||||
rating: 4.8,
|
||||
reviews: 124
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Meal Contribution',
|
||||
price: 15,
|
||||
originalPrice: 25,
|
||||
image: 'https://images.unsplash.com/photo-1565299624946-b28f40a0ca4b?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
|
||||
description: 'Help me focus on creating amazing projects',
|
||||
rating: 4.9,
|
||||
reviews: 89
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Project Boost',
|
||||
price: 25,
|
||||
originalPrice: 40,
|
||||
image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
|
||||
description: 'Support the development of new features',
|
||||
rating: 5.0,
|
||||
reviews: 67
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Monthly Patron',
|
||||
price: 50,
|
||||
originalPrice: 75,
|
||||
image: 'https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
|
||||
description: 'Ongoing support for continuous improvement',
|
||||
rating: 4.7,
|
||||
reviews: 45
|
||||
}
|
||||
];
|
||||
|
||||
const handleDonate = (productId: number) => {
|
||||
setDonatedItems(prev => new Set(prev).add(productId));
|
||||
// Here you would integrate with a payment processor like Stripe
|
||||
alert(`Thank you for your donation of $${products.find(p => p.id === productId)?.price}!`);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-background">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-text sm:text-4xl">
|
||||
Support My Creative Journey
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
|
||||
Instead of buying products, consider donating to support my creative journey
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{products.map((product) => (
|
||||
<div
|
||||
key={product.id}
|
||||
className="bg-background-light rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2 group border border-lines overflow-hidden"
|
||||
>
|
||||
<div className="relative overflow-hidden">
|
||||
<img
|
||||
className="w-full h-48 object-cover group-hover:scale-110 transition-transform duration-300"
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute top-4 left-4">
|
||||
<div className="bg-other text-background px-2 py-1 rounded-full text-xs font-medium">
|
||||
Donation
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-4 right-4 flex items-center space-x-1">
|
||||
<Star className="h-4 w-4 text-other fill-current" />
|
||||
<span className="text-text text-sm font-medium bg-background/70 px-1 rounded">
|
||||
{product.rating}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-semibold text-text mb-2">
|
||||
{product.name}
|
||||
</h3>
|
||||
<p className="text-lines mb-4 text-sm">
|
||||
{product.description}
|
||||
</p>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-2xl font-bold text-text">
|
||||
${product.price}
|
||||
</span>
|
||||
<span className="text-sm text-lines line-through">
|
||||
${product.originalPrice}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm text-lines">
|
||||
({product.reviews} reviews)
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleDonate(product.id)}
|
||||
disabled={donatedItems.has(product.id)}
|
||||
className={`w-full flex items-center justify-center px-4 py-3 rounded-lg text-sm font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-other focus:ring-offset-2 ${
|
||||
donatedItems.has(product.id)
|
||||
? 'bg-other/20 text-other cursor-not-allowed'
|
||||
: 'bg-other text-background hover:bg-lines transform hover:scale-105'
|
||||
}`}
|
||||
>
|
||||
{donatedItems.has(product.id) ? (
|
||||
<>
|
||||
<Heart className="h-5 w-5 mr-2 fill-current" />
|
||||
Donated!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ShoppingCart className="h-5 w-5 mr-2" />
|
||||
Donate Now
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-12">
|
||||
<p className="text-sm text-lines">
|
||||
All donations go towards improving my portfolio and creating more amazing projects
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default DonateShop;
|
||||
99
frontend/src/components/DroneVideoCarousel.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
const DroneVideoCarousel: React.FC = () => {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
|
||||
// Placeholder YouTube video IDs - replace with actual drone video IDs
|
||||
const videos = [
|
||||
{ id: 'dQw4w9WgXcQ', title: 'Drone Footage 1' },
|
||||
{ id: 'dQw4w9WgXcQ', title: 'Drone Footage 2' },
|
||||
{ id: 'dQw4w9WgXcQ', title: 'Drone Footage 3' },
|
||||
{ id: 'dQw4w9WgXcQ', title: 'Drone Footage 4' }
|
||||
];
|
||||
|
||||
const nextSlide = () => {
|
||||
setCurrentIndex((prevIndex) => (prevIndex + 1) % videos.length);
|
||||
};
|
||||
|
||||
const prevSlide = () => {
|
||||
setCurrentIndex((prevIndex) => (prevIndex - 1 + videos.length) % videos.length);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(nextSlide, 5000);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-background">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-text sm:text-4xl">
|
||||
Drone Videography
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
|
||||
Capturing stunning aerial perspectives through professional drone footage
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-4xl mx-auto">
|
||||
<div className="aspect-video bg-background-light rounded-xl overflow-hidden shadow-2xl border border-lines">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={currentIndex}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="w-full h-full"
|
||||
>
|
||||
<iframe
|
||||
src={`https://www.youtube.com/embed/${videos[currentIndex].id}?autoplay=0&mute=1&loop=1&playlist=${videos[currentIndex].id}`}
|
||||
title={videos[currentIndex].title}
|
||||
className="w-full h-full"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={prevSlide}
|
||||
className="absolute left-4 top-1/2 transform -translate-y-1/2 bg-boxes hover:bg-other text-text p-3 rounded-full transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2 focus:ring-offset-background"
|
||||
aria-label="Previous video"
|
||||
>
|
||||
<ChevronLeft className="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={nextSlide}
|
||||
className="absolute right-4 top-1/2 transform -translate-y-1/2 bg-boxes hover:bg-other text-text p-3 rounded-full transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2 focus:ring-offset-background"
|
||||
aria-label="Next video"
|
||||
>
|
||||
<ChevronRight className="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
<div className="flex justify-center mt-6 space-x-2">
|
||||
{videos.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setCurrentIndex(index)}
|
||||
className={`w-3 h-3 rounded-full transition-all duration-200 ${
|
||||
index === currentIndex
|
||||
? 'bg-other'
|
||||
: 'bg-lines hover:bg-boxes'
|
||||
}`}
|
||||
aria-label={`Go to video ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default DroneVideoCarousel;
|
||||
127
frontend/src/components/Footer.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import React from 'react';
|
||||
import { Instagram, Twitter, Youtube, Github, Linkedin, Gamepad2, Mail, Phone } from 'lucide-react';
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
const socialLinks = [
|
||||
{ name: 'Instagram', icon: Instagram, href: '#', color: 'hover:text-other' },
|
||||
{ name: 'Twitter', icon: Twitter, href: '#', color: 'hover:text-other' },
|
||||
{ name: 'YouTube', icon: Youtube, href: '#', color: 'hover:text-other' },
|
||||
{ name: 'GitHub', icon: Github, href: '#', color: 'hover:text-text' },
|
||||
{ name: 'LinkedIn', icon: Linkedin, href: '#', color: 'hover:text-other' },
|
||||
{ name: 'Steam', icon: Gamepad2, href: '#', color: 'hover:text-lines' }
|
||||
];
|
||||
|
||||
const footerLinks = [
|
||||
{
|
||||
title: 'Portfolio',
|
||||
links: [
|
||||
{ name: 'Web Development', href: '/portfolio/web' },
|
||||
{ name: 'Mobile Apps', href: '/portfolio/mobile' },
|
||||
{ name: 'UI/UX Design', href: '/portfolio/design' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Services',
|
||||
links: [
|
||||
{ name: 'Frontend Development', href: '/services/frontend' },
|
||||
{ name: 'Backend Development', href: '/services/backend' },
|
||||
{ name: 'Consulting', href: '/services/consulting' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Company',
|
||||
links: [
|
||||
{ name: 'About', href: '/about' },
|
||||
{ name: 'Blog', href: '/blog' },
|
||||
{ name: 'Contact', href: '/contact' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<footer className="bg-background text-text border-t border-lines">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{/* Brand Section */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="flex items-center">
|
||||
<span className="text-2xl font-bold text-other">
|
||||
Portfolio
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-4 text-lines text-sm">
|
||||
Creating exceptional digital experiences through innovative web development and beautiful design.
|
||||
</p>
|
||||
<div className="mt-6 flex space-x-4">
|
||||
{socialLinks.map((social) => {
|
||||
const IconComponent = social.icon;
|
||||
return (
|
||||
<a
|
||||
key={social.name}
|
||||
href={social.href}
|
||||
className={`text-lines ${social.color} transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-other focus:ring-offset-2 focus:ring-offset-background rounded`}
|
||||
aria-label={social.name}
|
||||
>
|
||||
<IconComponent className="h-5 w-5" />
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-6 space-y-2">
|
||||
<div className="flex items-center text-lines text-sm">
|
||||
<Mail className="h-4 w-4 mr-2" />
|
||||
hello@example.com
|
||||
</div>
|
||||
<div className="flex items-center text-lines text-sm">
|
||||
<Phone className="h-4 w-4 mr-2" />
|
||||
+1 (555) 123-4567
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Links Sections */}
|
||||
{footerLinks.map((section) => (
|
||||
<div key={section.title}>
|
||||
<h3 className="text-sm font-semibold text-text uppercase tracking-wider">
|
||||
{section.title}
|
||||
</h3>
|
||||
<ul className="mt-4 space-y-2">
|
||||
{section.links.map((link) => (
|
||||
<li key={link.name}>
|
||||
<a
|
||||
href={link.href}
|
||||
className="text-lines hover:text-other transition-colors duration-200 text-sm focus:outline-none focus:ring-2 focus:ring-other focus:ring-offset-2 focus:ring-offset-background rounded"
|
||||
>
|
||||
{link.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-boxes">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center">
|
||||
<p className="text-lines text-sm">
|
||||
© 2025 Portfolio. All rights reserved.
|
||||
</p>
|
||||
<div className="mt-4 md:mt-0 flex space-x-6">
|
||||
<a href="/privacy" className="text-lines hover:text-text text-sm transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-other focus:ring-offset-2 focus:ring-offset-background rounded">
|
||||
Privacy Policy
|
||||
</a>
|
||||
<a href="/terms" className="text-lines hover:text-text text-sm transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-other focus:ring-offset-2 focus:ring-offset-background rounded">
|
||||
Terms of Service
|
||||
</a>
|
||||
</div>
|
||||
<p className="text-lines text-sm mt-4 md:mt-0">
|
||||
Built with ❤️ by <a rel="nofollow" target="_blank" href="https://meku.dev" className="text-other hover:text-lines transition-colors duration-200">Meku.dev</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
186
frontend/src/components/Header.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { ChevronDown, Menu, X } from 'lucide-react';
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const handleMouseEnter = (menu: string) => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
setActiveDropdown(menu);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setActiveDropdown(null);
|
||||
}, 150);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const navigationItems = [
|
||||
{ name: 'Home', href: '/' },
|
||||
{
|
||||
name: 'Portfolio',
|
||||
href: '/portfolio',
|
||||
submenu: [
|
||||
{ name: 'Web Development', href: '/portfolio/web' },
|
||||
{ name: 'Mobile Apps', href: '/portfolio/mobile' },
|
||||
{ name: 'UI/UX Design', href: '/portfolio/design' },
|
||||
{ name: 'E-commerce', href: '/portfolio/ecommerce' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Services',
|
||||
href: '/services',
|
||||
submenu: [
|
||||
{ name: 'Frontend Development', href: '/services/frontend' },
|
||||
{ name: 'Backend Development', href: '/services/backend' },
|
||||
{ name: 'Full Stack Solutions', href: '/services/fullstack' },
|
||||
{ name: 'Consulting', href: '/services/consulting' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'About',
|
||||
href: '/about',
|
||||
submenu: [
|
||||
{ name: 'My Story', href: '/about/story' },
|
||||
{ name: 'Skills', href: '/about/skills' },
|
||||
{ name: 'Experience', href: '/about/experience' },
|
||||
{ name: 'Testimonials', href: '/about/testimonials' }
|
||||
]
|
||||
},
|
||||
{ name: 'Blog', href: '/blog' },
|
||||
{ name: 'Contact', href: '/contact' }
|
||||
];
|
||||
|
||||
return (
|
||||
<header className="bg-background shadow-lg sticky top-0 z-50">
|
||||
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" role="navigation" aria-label="Main navigation">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
{/* Logo */}
|
||||
<div className="flex-shrink-0">
|
||||
<a href="/" className="text-2xl font-bold text-other hover:text-lines transition-colors">
|
||||
Portfolio
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden md:block">
|
||||
<div className="ml-10 flex items-baseline space-x-4">
|
||||
{navigationItems.map((item) => (
|
||||
<div
|
||||
key={item.name}
|
||||
className="relative"
|
||||
onMouseEnter={() => item.submenu && handleMouseEnter(item.name)}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<a
|
||||
href={item.href}
|
||||
className="flex items-center px-3 py-2 rounded-md text-sm font-medium text-text hover:text-other hover:bg-background-light transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
|
||||
aria-haspopup={item.submenu ? 'true' : 'false'}
|
||||
aria-expanded={activeDropdown === item.name ? 'true' : 'false'}
|
||||
>
|
||||
{item.name}
|
||||
{item.submenu && (
|
||||
<ChevronDown
|
||||
className={`ml-1 h-4 w-4 transition-transform duration-200 ${
|
||||
activeDropdown === item.name ? 'rotate-180' : ''
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
{item.submenu && (
|
||||
<div
|
||||
className={`absolute left-0 mt-2 w-56 rounded-md shadow-lg bg-background-light ring-1 ring-lines ring-opacity-30 transition-all duration-200 ${
|
||||
activeDropdown === item.name
|
||||
? 'opacity-100 visible transform translate-y-0'
|
||||
: 'opacity-0 invisible transform -translate-y-2'
|
||||
}`}
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
>
|
||||
<div className="py-1">
|
||||
{item.submenu.map((subItem) => (
|
||||
<a
|
||||
key={subItem.name}
|
||||
href={subItem.href}
|
||||
className="block px-4 py-2 text-sm text-text hover:bg-boxes hover:text-other transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-inset"
|
||||
role="menuitem"
|
||||
>
|
||||
{subItem.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<div className="md:hidden">
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
className="inline-flex items-center justify-center p-2 rounded-md text-text hover:text-other hover:bg-background-light focus:outline-none focus:ring-2 focus:ring-inset focus:ring-lines transition-colors duration-200"
|
||||
aria-expanded={isMobileMenuOpen}
|
||||
aria-label="Toggle mobile menu"
|
||||
>
|
||||
{isMobileMenuOpen ? (
|
||||
<X className="block h-6 w-6" aria-hidden="true" />
|
||||
) : (
|
||||
<Menu className="block h-6 w-6" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation */}
|
||||
<div className={`md:hidden transition-all duration-300 ease-in-out ${
|
||||
isMobileMenuOpen ? 'max-h-screen opacity-100' : 'max-h-0 opacity-0 overflow-hidden'
|
||||
}`}>
|
||||
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3 bg-background-light rounded-lg mt-2">
|
||||
{navigationItems.map((item) => (
|
||||
<div key={item.name}>
|
||||
<a
|
||||
href={item.href}
|
||||
className="block px-3 py-2 rounded-md text-base font-medium text-text hover:text-other hover:bg-boxes transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
{item.submenu && (
|
||||
<div className="ml-4 space-y-1">
|
||||
{item.submenu.map((subItem) => (
|
||||
<a
|
||||
key={subItem.name}
|
||||
href={subItem.href}
|
||||
className="block px-3 py-2 rounded-md text-sm text-lines hover:text-other hover:bg-boxes transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
|
||||
>
|
||||
{subItem.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
59
frontend/src/components/Hero.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { ArrowRight, Download } from 'lucide-react';
|
||||
|
||||
const Hero: React.FC = () => {
|
||||
return (
|
||||
<section className="relative bg-background-light py-20 lg:py-32 overflow-hidden">
|
||||
<div className="absolute inset-0 bg-grid-pattern opacity-5"></div>
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="lg:grid lg:grid-cols-12 lg:gap-8 items-center">
|
||||
<div className="sm:text-center md:max-w-2xl md:mx-auto lg:col-span-6 lg:text-left">
|
||||
<h1 className="text-4xl font-bold text-text sm:text-5xl lg:text-6xl">
|
||||
<span className="block">Creative</span>
|
||||
<span className="block text-other">
|
||||
Developer
|
||||
</span>
|
||||
</h1>
|
||||
<p className="mt-6 text-lg text-lines sm:text-xl max-w-3xl">
|
||||
I craft exceptional digital experiences through innovative web development,
|
||||
combining cutting-edge technology with beautiful design to bring your ideas to life.
|
||||
</p>
|
||||
<div className="mt-8 sm:max-w-lg sm:mx-auto sm:text-center lg:text-left lg:mx-0">
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<a
|
||||
href="/portfolio"
|
||||
className="inline-flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-lg text-background bg-other hover:bg-lines focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-other transition-all duration-200 transform hover:scale-105"
|
||||
>
|
||||
View My Work
|
||||
<ArrowRight className="ml-2 h-5 w-5" aria-hidden="true" />
|
||||
</a>
|
||||
<a
|
||||
href="/resume.pdf"
|
||||
className="inline-flex items-center justify-center px-6 py-3 border-2 border-lines text-base font-medium rounded-lg text-text bg-transparent hover:bg-background hover:border-other hover:text-other focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-other transition-all duration-200"
|
||||
>
|
||||
<Download className="mr-2 h-5 w-5" aria-hidden="true" />
|
||||
Download Resume
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-12 relative sm:max-w-lg sm:mx-auto lg:mt-0 lg:max-w-none lg:mx-0 lg:col-span-6 lg:flex lg:items-center">
|
||||
<div className="relative mx-auto w-full rounded-lg shadow-lg lg:max-w-md">
|
||||
<div className="relative block w-full bg-boxes rounded-lg overflow-hidden">
|
||||
<img
|
||||
className="w-full h-96 object-cover"
|
||||
src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80"
|
||||
alt="Professional developer portrait"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-tr from-boxes/40 to-other/20"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Hero;
|
||||
110
frontend/src/components/Portfolio.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import { ExternalLink, Github } from 'lucide-react';
|
||||
|
||||
const Portfolio: React.FC = () => {
|
||||
const projects = [
|
||||
{
|
||||
title: 'E-Commerce Platform',
|
||||
description: 'A full-stack e-commerce solution with React, Node.js, and Stripe integration.',
|
||||
image: 'https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||
tags: ['React', 'Node.js', 'MongoDB', 'Stripe'],
|
||||
liveUrl: '#',
|
||||
githubUrl: '#'
|
||||
},
|
||||
{
|
||||
title: 'Task Management App',
|
||||
description: 'A collaborative project management tool with real-time updates and team features.',
|
||||
image: 'https://images.unsplash.com/photo-1611224923853-80b023f02d71?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||
tags: ['Vue.js', 'Firebase', 'Tailwind CSS'],
|
||||
liveUrl: '#',
|
||||
githubUrl: '#'
|
||||
},
|
||||
{
|
||||
title: 'Weather Dashboard',
|
||||
description: 'A responsive weather application with location-based forecasts and data visualization.',
|
||||
image: 'https://images.unsplash.com/photo-1504608524841-42fe6f032b4b?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||
tags: ['React', 'TypeScript', 'Chart.js', 'API'],
|
||||
liveUrl: '#',
|
||||
githubUrl: '#'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-background">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl font-bold text-text sm:text-4xl">
|
||||
Featured Projects
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
|
||||
A showcase of my recent work and creative solutions
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{projects.map((project, index) => (
|
||||
<div
|
||||
key={project.title}
|
||||
className="bg-background-light rounded-xl shadow-lg overflow-hidden hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2 group border border-lines"
|
||||
>
|
||||
<div className="relative overflow-hidden">
|
||||
<img
|
||||
className="w-full h-48 object-cover group-hover:scale-110 transition-transform duration-300"
|
||||
src={project.image}
|
||||
alt={project.title}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-background/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
<div className="absolute top-4 right-4 flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<a
|
||||
href={project.liveUrl}
|
||||
className="p-2 bg-boxes rounded-full hover:bg-other transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
|
||||
aria-label={`View ${project.title} live`}
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 text-text" />
|
||||
</a>
|
||||
<a
|
||||
href={project.githubUrl}
|
||||
className="p-2 bg-boxes rounded-full hover:bg-other transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
|
||||
aria-label={`View ${project.title} source code`}
|
||||
>
|
||||
<Github className="h-4 w-4 text-text" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-semibold text-text mb-2">
|
||||
{project.title}
|
||||
</h3>
|
||||
<p className="text-lines mb-4">
|
||||
{project.description}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="px-3 py-1 text-xs font-medium bg-boxes text-other rounded-full"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-12">
|
||||
<a
|
||||
href="/portfolio"
|
||||
className="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-lg text-background bg-other hover:bg-lines focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-other transition-all duration-200 transform hover:scale-105"
|
||||
>
|
||||
View All Projects
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Portfolio;
|
||||
115
frontend/src/components/Skills.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
import { Briefcase, Code, Database, Palette, Smartphone, Zap } from 'lucide-react';
|
||||
|
||||
const Skills: React.FC = () => {
|
||||
const experience = [
|
||||
{
|
||||
title: 'Senior Full-Stack Developer',
|
||||
company: 'Tech Innovations Inc.',
|
||||
period: '2022 - Present',
|
||||
description: 'Leading development of scalable web applications using React, Node.js, and cloud technologies.'
|
||||
},
|
||||
{
|
||||
title: 'Frontend Developer',
|
||||
company: 'Digital Solutions Ltd.',
|
||||
period: '2020 - 2022',
|
||||
description: 'Built responsive user interfaces and improved performance for e-commerce platforms.'
|
||||
},
|
||||
{
|
||||
title: 'Junior Developer',
|
||||
company: 'StartupXYZ',
|
||||
period: '2019 - 2020',
|
||||
description: 'Developed mobile applications and contributed to backend API development.'
|
||||
}
|
||||
];
|
||||
|
||||
const specificSkills = [
|
||||
{ name: 'React', level: 95, icon: Code },
|
||||
{ name: 'TypeScript', level: 90, icon: Code },
|
||||
{ name: 'Node.js', level: 85, icon: Database },
|
||||
{ name: 'Python', level: 80, icon: Code },
|
||||
{ name: 'UI/UX Design', level: 75, icon: Palette },
|
||||
{ name: 'Mobile Development', level: 70, icon: Smartphone }
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-background-light">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl font-bold text-text sm:text-4xl">
|
||||
Experience & Skills
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
|
||||
A comprehensive overview of my professional journey and technical expertise
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
{/* Experience Section */}
|
||||
<div>
|
||||
<div className="flex items-center mb-8">
|
||||
<div className="p-3 bg-other rounded-lg mr-4">
|
||||
<Briefcase className="h-6 w-6 text-background" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-text">Experience</h3>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
{experience.map((exp, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-background rounded-lg p-6 border border-lines"
|
||||
>
|
||||
<h4 className="text-lg font-semibold text-text mb-1">
|
||||
{exp.title}
|
||||
</h4>
|
||||
<p className="text-other font-medium mb-2">
|
||||
{exp.company} • {exp.period}
|
||||
</p>
|
||||
<p className="text-lines text-sm">
|
||||
{exp.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Specific Skills Section */}
|
||||
<div>
|
||||
<div className="flex items-center mb-8">
|
||||
<div className="p-3 bg-boxes rounded-lg mr-4">
|
||||
<Zap className="h-6 w-6 text-text" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-text">Specific Skills</h3>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
{specificSkills.map((skill, index) => {
|
||||
const IconComponent = skill.icon;
|
||||
return (
|
||||
<div key={skill.name} className="bg-background rounded-lg p-6 shadow-md border border-lines">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center">
|
||||
<div className="p-2 bg-boxes rounded-lg mr-3">
|
||||
<IconComponent className="h-5 w-5 text-other" />
|
||||
</div>
|
||||
<span className="font-semibold text-text">{skill.name}</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-lines">{skill.level}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-background-light rounded-full h-2">
|
||||
<div
|
||||
className="bg-other h-2 rounded-full transition-all duration-1000 ease-out"
|
||||
style={{ width: `${skill.level}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Skills;
|
||||
100
frontend/src/components/TradingGraph.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import { TrendingUp, BarChart3 } from 'lucide-react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
||||
|
||||
// Placeholder data - replace with actual Trading212 API data
|
||||
const data = [
|
||||
{ date: '2024-01', value: 1000 },
|
||||
{ date: '2024-02', value: 1200 },
|
||||
{ date: '2024-03', value: 1100 },
|
||||
{ date: '2024-04', value: 1400 },
|
||||
{ date: '2024-05', value: 1300 },
|
||||
{ date: '2024-06', value: 1600 }
|
||||
];
|
||||
|
||||
const TradingGraph: React.FC = () => {
|
||||
return (
|
||||
<section className="py-16 bg-background-light">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-text sm:text-4xl">
|
||||
Trading Performance
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
|
||||
Real-time insights from Trading212 portfolio tracking
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-background rounded-xl shadow-lg p-6 border border-lines">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-other rounded-lg">
|
||||
<BarChart3 className="h-6 w-6 text-background" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-text">Portfolio Value</h3>
|
||||
<p className="text-sm text-lines">Trading212 Integration</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-2xl font-bold text-text">$1,600</div>
|
||||
<div className="flex items-center text-sm text-other">
|
||||
<TrendingUp className="h-4 w-4 mr-1" />
|
||||
+12.5% this month
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={data}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--c-lines)" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
stroke="var(--c-lines)"
|
||||
fontSize={12}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
/>
|
||||
<YAxis
|
||||
stroke="var(--c-lines)"
|
||||
fontSize={12}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => `$${value}`}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'var(--c-background-light)',
|
||||
border: '1px solid var(--c-lines)',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
||||
color: 'var(--c-text)'
|
||||
}}
|
||||
labelStyle={{ color: 'var(--c-text)' }}
|
||||
formatter={(value: number) => [`$${value}`, 'Value']}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="value"
|
||||
stroke="var(--c-other)"
|
||||
strokeWidth={3}
|
||||
dot={{ fill: '#c026d3', strokeWidth: 2, r: 4 }}
|
||||
activeDot={{ r: 6, stroke: '#c026d3', strokeWidth: 2, fill: '#ffffff' }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-sm text-slate-500">
|
||||
API integration pending - displaying sample data
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default TradingGraph;
|
||||
83
frontend/src/components/WebsiteScreenshots.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import { ExternalLink } from 'lucide-react';
|
||||
|
||||
const WebsiteScreenshots: React.FC = () => {
|
||||
const websites = [
|
||||
{
|
||||
title: 'E-Commerce Platform',
|
||||
image: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||
url: '#',
|
||||
description: 'Modern online store with seamless checkout'
|
||||
},
|
||||
{
|
||||
title: 'Portfolio Website',
|
||||
image: 'https://images.unsplash.com/photo-1467232004584-a241de8bcf5d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||
url: '#',
|
||||
description: 'Creative showcase for digital artists'
|
||||
},
|
||||
{
|
||||
title: 'Business Landing Page',
|
||||
image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||
url: '#',
|
||||
description: 'Professional B2B service presentation'
|
||||
},
|
||||
{
|
||||
title: 'Blog Platform',
|
||||
image: 'https://images.unsplash.com/photo-1486312338219-ce68e2c6f44d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||
url: '#',
|
||||
description: 'Content management system for writers'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-background-light">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-text sm:text-4xl">
|
||||
Website Screenshots
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
|
||||
A glimpse of recent web development projects and designs
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{websites.map((website, index) => (
|
||||
<div
|
||||
key={website.title}
|
||||
className="group bg-background rounded-lg shadow-md hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1 border border-lines overflow-hidden"
|
||||
>
|
||||
<div className="relative overflow-hidden">
|
||||
<img
|
||||
className="w-full h-32 object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
src={website.image}
|
||||
alt={`${website.title} screenshot`}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-background/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end justify-end p-2">
|
||||
<a
|
||||
href={website.url}
|
||||
className="p-2 bg-boxes rounded-full hover:bg-other transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
|
||||
aria-label={`View ${website.title}`}
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 text-text" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<h3 className="text-sm font-semibold text-text mb-1">
|
||||
{website.title}
|
||||
</h3>
|
||||
<p className="text-xs text-lines">
|
||||
{website.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebsiteScreenshots;
|
||||
26
frontend/src/pages/About.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import Header from '../components/Header';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
const About: React.FC = () => {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Header />
|
||||
<main className="py-20">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-slate-800 sm:text-5xl">
|
||||
About
|
||||
</h1>
|
||||
<p className="mt-4 text-lg text-slate-600 max-w-2xl mx-auto">
|
||||
Ask Meku to generate content for this page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
26
frontend/src/pages/Blog.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import Header from '../components/Header';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
const Blog: React.FC = () => {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Header />
|
||||
<main className="py-20">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-slate-800 sm:text-5xl">
|
||||
Blog
|
||||
</h1>
|
||||
<p className="mt-4 text-lg text-slate-600 max-w-2xl mx-auto">
|
||||
Ask Meku to generate content for this page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Blog;
|
||||
26
frontend/src/pages/Contact.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import Header from '../components/Header';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
const Contact: React.FC = () => {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Header />
|
||||
<main className="py-20">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-slate-800 sm:text-5xl">
|
||||
Contact
|
||||
</h1>
|
||||
<p className="mt-4 text-lg text-slate-600 max-w-2xl mx-auto">
|
||||
Ask Meku to generate content for this page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
30
frontend/src/pages/Home.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import Header from '../components/Header';
|
||||
import Hero from '../components/Hero';
|
||||
import Skills from '../components/Skills';
|
||||
import Portfolio from '../components/Portfolio';
|
||||
import DroneVideoCarousel from '../components/DroneVideoCarousel';
|
||||
import WebsiteScreenshots from '../components/WebsiteScreenshots';
|
||||
import TradingGraph from '../components/TradingGraph';
|
||||
import DonateShop from '../components/DonateShop';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
const Home: React.FC = () => {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Header />
|
||||
<main>
|
||||
<Hero />
|
||||
<Skills />
|
||||
<Portfolio />
|
||||
<DroneVideoCarousel />
|
||||
<WebsiteScreenshots />
|
||||
<TradingGraph />
|
||||
<DonateShop />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
179
frontend/src/pages/NotFound.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import React from 'react';
|
||||
|
||||
const NotFound: React.FC = () => {
|
||||
return (
|
||||
<section className="relative flex py-10 min-h-screen items-center justify-center overflow-hidden bg-black">
|
||||
<div className="mx-auto relative z-30 w-full max-w-[600px] text-center px-4">
|
||||
{/* Large 404 Text */}
|
||||
<div className="mb-8">
|
||||
<svg
|
||||
width="472"
|
||||
height="158"
|
||||
viewBox="0 0 472 158"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M26.4028 0.5224C29.8616 0.5224 32.6655 3.3263 32.6655 6.7851V58.7187H75.8726V9.4306C75.8726 5.97182 78.6765 3.16794 82.1353 3.16791H102.236C105.694 3.16817 108.499 5.97196 108.499 9.4306V151.215C108.498 154.673 105.694 157.477 102.235 157.477H82.1353C78.6766 157.477 75.8727 154.673 75.8726 151.215V91.3437H23.0571C19.5983 91.3437 16.7944 88.5398 16.7944 85.081V78.1181H6.30322C2.84444 78.1181 0.0405444 75.3142 0.0405273 71.8554V6.7851C0.0405355 3.32631 2.84444 0.522409 6.30322 0.5224H26.4028Z"
|
||||
fill="url(#paint0_linear_11881_2293)"
|
||||
/>
|
||||
<path
|
||||
d="M262.328 82.4706C263.99 82.4708 265.338 83.8187 265.338 85.4814V97.8544H277.712C279.375 97.8546 280.723 99.2018 280.723 100.864V116.31C280.723 117.973 279.375 119.321 277.712 119.321H260.835C259.173 119.321 257.825 117.973 257.825 116.31V103.937H214.175V116.31C214.175 117.973 212.827 119.321 211.165 119.321H194.289C192.626 119.321 191.278 117.973 191.278 116.31V100.864C191.278 99.2017 192.626 97.8544 194.289 97.8544H207.02V85.4814C207.02 83.8186 208.368 82.4706 210.031 82.4706H262.328Z"
|
||||
fill="url(#paint1_linear_11881_2293)"
|
||||
/>
|
||||
<path
|
||||
d="M222.614 41.3251C224.277 41.3251 225.625 42.6731 225.625 44.3359V59.7812C225.625 61.4439 224.277 62.7919 222.614 62.7919H205.737C204.074 62.7917 202.727 61.4438 202.727 59.7812V44.3359C202.727 42.6733 204.074 41.3254 205.737 41.3251H222.614Z"
|
||||
fill="url(#paint2_linear_11881_2293)"
|
||||
/>
|
||||
<path
|
||||
d="M266.263 41.3251C267.926 41.3252 269.274 42.6732 269.274 44.3359V59.7812C269.274 61.4439 267.926 62.7918 266.263 62.7919H249.386C247.724 62.7918 246.375 61.4439 246.375 59.7812V44.3359C246.375 42.6732 247.724 41.3252 249.386 41.3251H266.263Z"
|
||||
fill="url(#paint3_linear_11881_2293)"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M291.231 3.16693C313.322 3.16693 331.231 21.0755 331.231 43.1669V117.477L331.218 118.51C330.671 140.124 312.977 157.477 291.231 157.477H180.769C158.678 157.477 140.769 139.569 140.769 117.477V43.1669C140.769 21.0755 158.678 3.16693 180.769 3.16693H291.231ZM180.769 27.1669C171.932 27.1669 164.769 34.3304 164.769 43.1669V117.477C164.769 126.314 171.932 133.477 180.769 133.477H291.231C300.068 133.477 307.231 126.314 307.231 117.477V43.1669C307.231 34.3304 300.068 27.1669 291.231 27.1669H180.769Z"
|
||||
fill="url(#paint4_linear_11881_2293)"
|
||||
/>
|
||||
<path
|
||||
d="M389.865 0.5224C393.324 0.522421 396.127 3.32632 396.127 6.7851V58.7187H439.334V9.4306C439.334 5.9718 442.138 3.16791 445.597 3.16791H465.697C469.156 3.16791 471.959 5.9718 471.959 9.4306V151.215C471.959 154.673 469.155 157.477 465.697 157.477H445.597C442.138 157.477 439.335 154.673 439.334 151.215V91.3437H386.518C383.059 91.3436 380.255 88.5397 380.255 85.081V78.1181H369.765C366.306 78.1181 363.502 75.3142 363.502 71.8554V6.7851C363.502 3.3263 366.306 0.5224 369.765 0.5224H389.865Z"
|
||||
fill="url(#paint5_linear_11881_2293)"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_11881_2293"
|
||||
x1="471.959"
|
||||
y1="157.477"
|
||||
x2="448.654"
|
||||
y2="-49.8952"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#3D404D" />
|
||||
<stop offset="1" stopColor="#8F95B2" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_11881_2293"
|
||||
x1="471.959"
|
||||
y1="157.477"
|
||||
x2="448.654"
|
||||
y2="-49.8952"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#3D404D" />
|
||||
<stop offset="1" stopColor="#8F95B2" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_11881_2293"
|
||||
x1="471.959"
|
||||
y1="157.477"
|
||||
x2="448.654"
|
||||
y2="-49.8952"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#3D404D" />
|
||||
<stop offset="1" stopColor="#8F95B2" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_11881_2293"
|
||||
x1="471.959"
|
||||
y1="157.477"
|
||||
x2="448.654"
|
||||
y2="-49.8952"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#3D404D" />
|
||||
<stop offset="1" stopColor="#8F95B2" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_11881_2293"
|
||||
x1="471.959"
|
||||
y1="157.477"
|
||||
x2="448.654"
|
||||
y2="-49.8952"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#3D404D" />
|
||||
<stop offset="1" stopColor="#8F95B2" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint5_linear_11881_2293"
|
||||
x1="471.959"
|
||||
y1="157.477"
|
||||
x2="448.654"
|
||||
y2="-49.8952"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#3D404D" />
|
||||
<stop offset="1" stopColor="#8F95B2" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h1 className="mb-4 text-3xl font-bold text-white sm:text-4xl">
|
||||
OPPS! Page Not Found
|
||||
</h1>
|
||||
<p className="mb-8 text-base text-white/60 sm:text-lg">
|
||||
We can't seem to find the page you are looking for!
|
||||
</p>
|
||||
<a
|
||||
href={typeof window !== 'undefined' ? window.location.origin : '/'}
|
||||
className="inline-flex items-center gap-2 rounded-full bg-white px-6 py-3 text-sm font-medium text-black transition-colors hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-black"
|
||||
>
|
||||
Back to homepage
|
||||
</a>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="mt-16">
|
||||
<p className="text-sm text-gray-600">
|
||||
© {new Date().getFullYear()} - Meku.dev
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 bg-[url(https://meku.dev/images/grain.png)] bg-cover bg-center opacity-60 mix-blend-soft-light z-20"></div>
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 z-10">
|
||||
<svg
|
||||
width="2192"
|
||||
height="771"
|
||||
viewBox="0 0 2192 771"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g opacity="0.35" filter="url(#filter0_f_3740_75)">
|
||||
<path
|
||||
d="M199.999 258.919C199.999 86.6144 601.152 347.404 1096 347.404C1590.85 347.404 1992 86.6146 1992 258.919C1992 431.223 1590.85 570.904 1096 570.904C601.152 570.904 199.999 431.223 199.999 258.919Z"
|
||||
fill="#C0C2CF"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_3740_75"
|
||||
x="-0.000732422"
|
||||
y="0.0515137"
|
||||
width="2192"
|
||||
height="770.852"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feGaussianBlur
|
||||
stdDeviation="100"
|
||||
result="effect1_foregroundBlur_3740_75"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFound;
|
||||
26
frontend/src/pages/Portfolio.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import Header from '../components/Header';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
const Portfolio: React.FC = () => {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Header />
|
||||
<main className="py-20">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-slate-800 sm:text-5xl">
|
||||
Portfolio
|
||||
</h1>
|
||||
<p className="mt-4 text-lg text-slate-600 max-w-2xl mx-auto">
|
||||
Ask Meku to generate content for this page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Portfolio;
|
||||