api done
This commit is contained in:
5
backend/thirdparty/gopay/apps.py
vendored
5
backend/thirdparty/gopay/apps.py
vendored
@@ -3,4 +3,7 @@ from django.apps import AppConfig
|
||||
|
||||
class GopayConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'gopay'
|
||||
# Must be the full dotted path to this app package
|
||||
name = 'thirdparty.gopay'
|
||||
# Optional short label used in DB table names and admin
|
||||
label = 'gopay'
|
||||
|
||||
9
backend/thirdparty/trading212/serializers.py
vendored
9
backend/thirdparty/trading212/serializers.py
vendored
@@ -1,11 +1,2 @@
|
||||
# thirdparty/trading212/serializers.py
|
||||
from rest_framework import serializers
|
||||
|
||||
class Trading212AccountCashSerializer(serializers.Serializer):
|
||||
blocked = serializers.FloatField()
|
||||
free = serializers.FloatField()
|
||||
invested = serializers.FloatField()
|
||||
pieCash = serializers.FloatField()
|
||||
ppl = serializers.FloatField()
|
||||
result = serializers.FloatField()
|
||||
total = serializers.FloatField()
|
||||
|
||||
3
backend/thirdparty/trading212/tests.py
vendored
3
backend/thirdparty/trading212/tests.py
vendored
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
5
backend/thirdparty/trading212/urls.py
vendored
5
backend/thirdparty/trading212/urls.py
vendored
@@ -1,6 +1,7 @@
|
||||
from django.urls import path
|
||||
from .views import Trading212AccountCashView # Replace with actual view class
|
||||
from .views import Trading212AccountCashView, Trading212SummaryView
|
||||
|
||||
urlpatterns = [
|
||||
path('your-endpoint/', Trading212AccountCashView.as_view(), name='trading212-endpoint'),
|
||||
path('equity/cash/', Trading212AccountCashView.as_view(), name='trading212-cash'),
|
||||
path('equity/summary/', Trading212SummaryView.as_view(), name='trading212-summary'),
|
||||
]
|
||||
60
backend/thirdparty/trading212/views.py
vendored
60
backend/thirdparty/trading212/views.py
vendored
@@ -1,38 +1,58 @@
|
||||
# thirdparty/trading212/views.py
|
||||
import os
|
||||
import base64
|
||||
import logging
|
||||
import requests
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from .serializers import Trading212AccountCashSerializer
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
api_key = os.getenv("ID_API_KEY_TRADING212")
|
||||
api_secret = os.getenv("API_KEY_TRADING212")
|
||||
|
||||
base_url = "https://live.trading212.com"
|
||||
|
||||
class Trading212AccountCashView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
permission_classes = [AllowAny]
|
||||
authentication_classes = []
|
||||
|
||||
@extend_schema(
|
||||
tags=["trading212"],
|
||||
summary="Get Trading212 account cash",
|
||||
responses=Trading212AccountCashSerializer
|
||||
summary="Get Trading212 account cash"
|
||||
)
|
||||
def get(self, request):
|
||||
api_key = os.getenv("API_KEY_TRADING212")
|
||||
url = f"{base_url}/api/v0/equity/account/cash"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
response = requests.get(url, auth=(api_key, api_secret))
|
||||
|
||||
url = "https://api.trading212.com/api/v0/equity/account/cash"
|
||||
logger.info("Trading212 Account Cash Response:")
|
||||
logger.info(response)
|
||||
|
||||
try:
|
||||
resp = requests.get(url, headers=headers, timeout=10)
|
||||
resp.raise_for_status()
|
||||
except requests.RequestException as exc:
|
||||
return Response({"error": str(exc)}, status=400)
|
||||
data = response.json()
|
||||
|
||||
data = resp.json()
|
||||
serializer = Trading212AccountCashSerializer(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return Response(serializer.data)
|
||||
return Response(data)
|
||||
|
||||
class Trading212SummaryView(APIView):
|
||||
permission_classes = [AllowAny]
|
||||
authentication_classes = []
|
||||
|
||||
@extend_schema(
|
||||
tags=["trading212"],
|
||||
summary="Get Trading212 account summary"
|
||||
)
|
||||
def get(self, request):
|
||||
url = f"{base_url}/api/v0/equity/portfolio"
|
||||
response = requests.get(url, auth=(api_key, api_secret))
|
||||
|
||||
logger.info("Trading212 Account Summary Response:")
|
||||
logger.info(response)
|
||||
|
||||
data = response.json()
|
||||
|
||||
return Response(data)
|
||||
@@ -31,9 +31,14 @@ export default function Footer() {
|
||||
|
||||
function Social({ href, label, children }: { href: string; label: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<a href={href} target="_blank" rel="noreferrer" aria-label={label}
|
||||
className="rounded p-2 transition-transform duration-200 hover:scale-110 hover:text-[var(--c-other)]">
|
||||
{children}
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label={label}
|
||||
className="group relative inline-flex items-center justify-center w-12 h-12 rounded-xl bg-brandGradient text-white shadow-glow transition-transform duration-200 hover:scale-110"
|
||||
>
|
||||
<span className="text-xl group-hover:scale-110 transition-transform">{children}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,20 +17,19 @@ export default function ContactMeForm() {
|
||||
if (!opened) {
|
||||
setOpened(true)
|
||||
setOpeningBehind(false)
|
||||
setContentMoveUp(false)
|
||||
setContentMoveUp(false)
|
||||
// Wait for the rotate-opening animation to finish before moving content up
|
||||
// The actual moveUp will be handled in onTransitionEnd
|
||||
} else {
|
||||
setContentMoveUp(false)
|
||||
setOpeningBehind(false)
|
||||
setTimeout(() => setOpened(false), 1000) // match transition duration
|
||||
setTimeout(() => setOpened(false), 1000) // match transition duration
|
||||
}
|
||||
}
|
||||
|
||||
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
|
||||
if (opened && e.propertyName === "transform") {
|
||||
setContentMoveUp(true)
|
||||
// Move the opening behind after the animation
|
||||
setContentMoveUp(true)
|
||||
setTimeout(() => setOpeningBehind(true), 10)
|
||||
}
|
||||
if (!opened && e.propertyName === "transform") {
|
||||
@@ -39,6 +38,7 @@ export default function ContactMeForm() {
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<div className={styles["contact-me"]}>
|
||||
<div
|
||||
ref={openingRef}
|
||||
@@ -56,11 +56,10 @@ export default function ContactMeForm() {
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={
|
||||
contentMoveUp
|
||||
? `${styles.content} ${styles["content-moveup"]}`
|
||||
: styles.content
|
||||
}
|
||||
className={[
|
||||
styles.content,
|
||||
contentMoveUp ? styles["content-moveup"] : ''
|
||||
].filter(Boolean).join(' ')}
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
.contact-me {
|
||||
margin: 5em auto;
|
||||
margin: 15em auto;
|
||||
margin-top: 15em;
|
||||
margin-bottom: 3em;
|
||||
position: relative;
|
||||
|
||||
aspect-ratio: 16 / 9;
|
||||
|
||||
background-color: #c8c8c8;
|
||||
background: var(--c-background-light);
|
||||
max-width: 100vw;
|
||||
border: 2px solid var(--c-lines);
|
||||
border-radius: 0.75em;
|
||||
width: 30em;
|
||||
}
|
||||
.contact-me + .mail-box{
|
||||
|
||||
}
|
||||
/* .mail-box sibling styles were unused; removed to satisfy linter */
|
||||
|
||||
.contact-me .opening {
|
||||
width: 100%;
|
||||
@@ -17,21 +18,17 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
z-index: 2;
|
||||
transform-origin: top;
|
||||
|
||||
padding-top: 4em;
|
||||
|
||||
clip-path: polygon(0 0, 100% 0, 50% 50%);
|
||||
background-color: #d2d2d2;
|
||||
|
||||
transition: all 1s ease;
|
||||
|
||||
background: linear-gradient(135deg, var(--c-boxes), var(--c-other));
|
||||
transition: transform 1s ease, background 0.5s ease;
|
||||
text-align: center;
|
||||
color: var(--c-text);
|
||||
}
|
||||
.rotate-opening{
|
||||
background-color: #c8c8c8;
|
||||
background: linear-gradient(135deg, var(--c-background-light), var(--c-boxes));
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
|
||||
@@ -52,22 +49,22 @@
|
||||
.content-moveup{
|
||||
transform: translateY(-70%);
|
||||
}
|
||||
.content-moveup-index {
|
||||
z-index: 2 !important;
|
||||
}
|
||||
.content-moveup-index { z-index: 2 !important; }
|
||||
|
||||
.contact-me .content form{
|
||||
width: 80%;
|
||||
width: 90%;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
margin: auto;
|
||||
background-color: #deefff;
|
||||
padding: 1em;
|
||||
border: 0.5em dashed #88d4ed;
|
||||
border-radius: 0.25em;
|
||||
background: color-mix(in hsl, var(--c-background-light), transparent 35%);
|
||||
backdrop-filter: blur(6px);
|
||||
padding: 1.5em;
|
||||
border: 0.35em dashed var(--c-lines);
|
||||
border-radius: 0.75em;
|
||||
color: var(--c-text);
|
||||
}
|
||||
.contact-me .content form div{
|
||||
width: -webkit-fill-available;
|
||||
@@ -77,26 +74,37 @@
|
||||
.contact-me .content form input[type=submit]{
|
||||
margin: auto;
|
||||
border: none;
|
||||
background: #4ca4d5;
|
||||
color: #ffffff;
|
||||
padding: 1em 1.5em;
|
||||
background: linear-gradient(135deg, var(--c-other), var(--c-boxes));
|
||||
color: var(--c-text);
|
||||
padding: 0.85em 1.5em;
|
||||
cursor: pointer;
|
||||
border-radius: 1em;
|
||||
border-radius: 0.75em;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 0 12px -3px rgba(112,162,136,0.5);
|
||||
transition: transform 0.2s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
.contact-me .content form input[type=submit]:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0 20px -2px rgba(112,162,136,0.65);
|
||||
}
|
||||
|
||||
.contact-me .content form input[type=text],
|
||||
.contact-me .content form input[type=email],
|
||||
.contact-me .content form textarea{
|
||||
background-color: #bfe8ff;
|
||||
border: none;
|
||||
border-bottom: 0.15em solid #064c7d;
|
||||
padding: 0.5em;
|
||||
|
||||
background: var(--c-background-light);
|
||||
border: 1px solid var(--c-lines);
|
||||
padding: 0.6em 0.75em;
|
||||
border-radius: 0.5em;
|
||||
color: var(--c-text);
|
||||
}
|
||||
.contact-me .content form input[type=text]::placeholder,
|
||||
.contact-me .content form input[type=email]::placeholder,
|
||||
.contact-me .content form textarea::placeholder{ color: color-mix(in srgb, var(--c-lines), white 25%); }
|
||||
|
||||
.opening-behind { z-index: 0 !important; }
|
||||
|
||||
.contact-me .cover {
|
||||
border-radius: 1em;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
@@ -104,9 +112,10 @@
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
clip-path: polygon(0 0, 50% 50%, 100% 0, 100% 100%, 0 100%);
|
||||
background-color: #f0f0f0;
|
||||
background: var(--c-background);
|
||||
}
|
||||
.contact-me .triangle{
|
||||
border-radius: 1em;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
@@ -114,7 +123,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
clip-path: polygon(100% 0, 0 100%, 100% 100%);
|
||||
background-color: rgb(255 255 255);
|
||||
background: linear-gradient(135deg, var(--c-boxes), var(--c-background-light));
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
@@ -140,6 +149,13 @@
|
||||
@media only screen and (max-width: 990px){
|
||||
.contact-me{
|
||||
aspect-ratio: unset;
|
||||
margin-top: 7ch;
|
||||
margin-top: 3ch;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
.contact-me .opening,
|
||||
.contact-me .cover,
|
||||
.contact-me .triangle { display: none; }
|
||||
.contact-me .content { position: relative; width: 100%; transform: none !important; }
|
||||
.contact-me .content form { backdrop-filter: none; border: 1px solid var(--c-lines); }
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #c2a67d;
|
||||
color: #5e5747;
|
||||
/* Use brand palette accents and lines for door */
|
||||
background-color: var(--c-background-light);
|
||||
color: var(--c-text);
|
||||
|
||||
border-radius: 1em;
|
||||
|
||||
@@ -50,7 +51,7 @@
|
||||
}
|
||||
|
||||
.door i{
|
||||
color: #5e5747;
|
||||
color: var(--c-text);
|
||||
font-size: 5em;
|
||||
display: inline-block;
|
||||
animation: shake 0.4s ease-in-out infinite;
|
||||
@@ -70,8 +71,8 @@
|
||||
left: 0;
|
||||
padding: 1em 3em;
|
||||
padding-bottom: 0;
|
||||
background-color: #cdc19c;
|
||||
color: #5e5747;
|
||||
background-color: var(--c-background-light);
|
||||
color: var(--c-text);
|
||||
border-top-left-radius: 1em;
|
||||
border-top-right-radius: 1em;
|
||||
}
|
||||
@@ -125,7 +126,7 @@
|
||||
.portfolio div {
|
||||
width: 100%;
|
||||
padding: 3em;
|
||||
background-color: #cdc19c;
|
||||
background-color: var(--c-background-light);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
@@ -141,7 +142,7 @@
|
||||
.portfolio div article {
|
||||
display: flex;
|
||||
border-radius: 0em;
|
||||
background-color: #9c885c;
|
||||
background-color: var(--c-boxes);
|
||||
width: 30%;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -4,26 +4,28 @@ import type { ReactNode } from 'react';
|
||||
type Tier = { id: string; title: string; price: number; desc: string; color: string; icon: ReactNode };
|
||||
|
||||
const tiers: Tier[] = [
|
||||
{ id: 'coffee', title: 'Coffee', price: 3, desc: 'Fuel late-night coding sessions.', color: '#d97706', icon: <FaCoffee /> },
|
||||
{ id: 'battery', title: 'Drone Battery', price: 30, desc: 'Extend aerial filming time.', color: '#16a34a', icon: <FaBatteryFull /> },
|
||||
{ id: 'gpu', title: 'GPU Upgrade', price: 200, desc: 'Speed up rendering and ML tasks.', color: '#6366f1', icon: <FaMicrochip /> },
|
||||
{ id: 'coffee', title: 'Coffee', price: 3, desc: 'Fuel late-night coding sessions.', color: 'var(--c-other)', icon: <FaCoffee /> },
|
||||
{ id: 'battery', title: 'Drone Battery', price: 30, desc: 'Extend aerial filming time.', color: 'var(--c-other)', icon: <FaBatteryFull /> },
|
||||
{ id: 'gpu', title: 'GPU Upgrade', price: 200, desc: 'Speed up rendering and ML tasks.', color: 'var(--c-other)', icon: <FaMicrochip /> },
|
||||
];
|
||||
|
||||
export default function DonationShop() {
|
||||
return (
|
||||
<section id="shop" className="section">
|
||||
<div className="container">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-2 text-rainbow">Support My Creative Journey</h2>
|
||||
<p className="text-gray-300">Instead of buying products, consider donating to support my creative journey.</p>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-2 text-rainbow">Support My Creative Journey</h2>
|
||||
<p className="text-brand-text/80">Instead of buying products, consider donating to support my creative journey.</p>
|
||||
<div className="grid md:grid-cols-3 gap-6 mt-6">
|
||||
{tiers.map(t => (
|
||||
<div key={t.id} className="card p-5 flex flex-col">
|
||||
<div className="text-5xl mb-3" style={{ color: t.color }}>{t.icon}</div>
|
||||
<div className="text-5xl mb-3 inline-flex items-center justify-center w-20 h-20 rounded-xl bg-brandGradient text-white shadow-glow">
|
||||
{t.icon}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold" style={{ color: t.color }}>{t.title}</h3>
|
||||
<p className="text-[var(--c-lines)] mt-1">{t.desc}</p>
|
||||
<div className="mt-auto flex items-center justify-between pt-4">
|
||||
<span className="text-2xl font-bold" style={{ color: t.color }}>${t.price}</span>
|
||||
<button className="px-4 py-2 rounded-lg font-semibold text-white bg-gradient-to-r from-fuchsia-600 to-orange-500 hover:shadow-lg hover:shadow-fuchsia-600/25 transition-all" onClick={() => alert(`Thank you for supporting with ${t.title}!`)}>Donate</button>
|
||||
<button className="px-4 py-2 rounded-lg font-semibold text-brand-text bg-gradient-to-r from-[var(--c-other)] to-[var(--c-lines)] hover:shadow-glow transition-all" onClick={() => alert(`Thank you for supporting with ${t.title}!`)}>Donate</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -13,11 +13,11 @@ export default function HeroCarousel() {
|
||||
return (
|
||||
<section id="home" className="relative min-h-[80vh] md:min-h-[85vh] flex items-center justify-center overflow-hidden">
|
||||
{/* Background Gradient and animated glows */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-slate-800 via-slate-900 to-black -z-10" />
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-[var(--c-background-light)] via-[var(--c-background)] to-[var(--c-background)] -z-10" />
|
||||
<div className="absolute inset-0 -z-10 pointer-events-none">
|
||||
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-fuchsia-600/10 rounded-full blur-3xl animate-pulse" />
|
||||
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-orange-500/10 rounded-full blur-3xl animate-pulse delay-1000" />
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-80 h-80 bg-violet-400/10 rounded-full blur-3xl animate-pulse delay-2000" />
|
||||
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-brand-accent/10 rounded-full blur-3xl animate-pulse" />
|
||||
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-brand-lines/10 rounded-full blur-3xl animate-pulse delay-1000" />
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-80 h-80 bg-brand-boxes/10 rounded-full blur-3xl animate-pulse delay-2000" />
|
||||
</div>
|
||||
|
||||
<div className="relative container mx-auto px-4 py-10 grid lg:grid-cols-2 gap-10 items-center">
|
||||
@@ -25,18 +25,18 @@ export default function HeroCarousel() {
|
||||
<div className="text-center lg:text-left">
|
||||
<h1 className="text-4xl md:text-6xl font-bold mb-4 leading-tight">
|
||||
<span className="text-rainbow">Welcome to</span><br />
|
||||
<span className="text-white">Vontor.cz</span>
|
||||
<span className="text-brand-text">Vontor.cz</span>
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl text-gray-300 mb-6">Creative Tech & Design by <span className="text-fuchsia-600 font-semibold">Bruno Vontor</span></p>
|
||||
<p className="text-lg md:text-xl text-brand-text/80 mb-6">Creative Tech & Design by <span className="text-brand-accent font-semibold">Bruno Vontor</span></p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
|
||||
<a href="#portfolio" className="px-8 py-3 bg-gradient-to-r from-fuchsia-600 to-orange-500 text-white font-semibold rounded-lg hover:shadow-lg hover:shadow-fuchsia-600/25 transition-all duration-300 transform hover:scale-105">View Portfolio</a>
|
||||
<a href="#contact" className="px-8 py-3 border-2 border-violet-400 text-violet-400 font-semibold rounded-lg hover:bg-violet-400 hover:text-white transition-all duration-300">Get In Touch</a>
|
||||
<a href="#portfolio" className="px-8 py-3 bg-gradient-to-r from-[var(--c-other)] to-[var(--c-lines)] text-brand-text font-semibold rounded-lg hover:shadow-glow transition-all duration-300 transform hover:scale-105">View Portfolio</a>
|
||||
<a href="#contact" className="px-8 py-3 border-2 border-brand-lines text-brand-lines font-semibold rounded-lg hover:bg-brand-lines hover:text-brand-bg transition-all duration-300">Get In Touch</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Video carousel */}
|
||||
<div className="relative">
|
||||
<div className="relative aspect-video bg-slate-800 rounded-xl overflow-hidden shadow-2xl">
|
||||
<div className="relative aspect-video bg-brand-bgLight rounded-xl overflow-hidden shadow-2xl">
|
||||
{videos.map((v,i) => (
|
||||
<iframe
|
||||
key={v}
|
||||
@@ -47,12 +47,12 @@ export default function HeroCarousel() {
|
||||
allowFullScreen
|
||||
/>
|
||||
))}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-brand-bg/60 to-transparent pointer-events-none" />
|
||||
</div>
|
||||
{/* Indicators */}
|
||||
<div className="flex justify-center mt-4 space-x-2">
|
||||
{videos.map((_,i) => (
|
||||
<button key={i} onClick={()=>setIndex(i)} aria-label={`Go to slide ${i+1}`} className={`w-3 h-3 rounded-full transition-all duration-300 ${i===index? 'bg-fuchsia-600':'bg-gray-600 hover:bg-gray-400'}`} />
|
||||
<button key={i} onClick={()=>setIndex(i)} aria-label={`Go to slide ${i+1}`} className={`w-3 h-3 rounded-full transition-all duration-300 ${i===index? 'bg-brand-accent':'bg-brand-lines/40 hover:bg-brand-lines/60'}`} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
|
||||
export default function HostingSecuritySection() {
|
||||
return (
|
||||
<section id="hosting" className="py-20">
|
||||
<div className="container mx-auto px-4">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6 text-rainbow">Hosting & Protection</h2>
|
||||
<div className="card p-6 md:p-8">
|
||||
<p className="text-gray-300">We host our applications ourselves, which reduces hosting costs as projects scale.</p>
|
||||
<p className="text-gray-300 mt-2">All websites are protected by Cloudflare and optimized for performance.</p>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6 text-rainbow">Hosting & Protection</h2>
|
||||
<div className="card p-6 md:p-8">
|
||||
<p className="text-brand-text opacity-80">We host our applications ourselves, which reduces hosting costs as projects scale.</p>
|
||||
<p className="text-brand-text opacity-80 mt-2">All websites are protected by Cloudflare and optimized for performance.</p>
|
||||
<div className="grid grid-cols-3 md:grid-cols-6 gap-4 text-center text-sm mt-6">
|
||||
{['Server', 'Cloudflare', 'Docker', 'SSL', 'Monitoring', 'Scaling'].map(item => (
|
||||
<div key={item} className="p-3 rounded-lg bg-slate-700/50 text-violet-300 transition-transform duration-200 hover:scale-105">{item}</div>
|
||||
<div key={item} className="p-3 rounded-lg bg-brandGradient text-white shadow-glow transition-transform duration-200 hover:scale-105 flex flex-col items-center justify-center">
|
||||
<span className="text-xs tracking-wide">{item}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ nav{
|
||||
background: linear-gradient(117deg, rgba(34,34,34,1) 0%, rgba(59,54,54,1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#222222",endColorstr="#3b3636",GradientType=1);*/
|
||||
|
||||
color: #fff;
|
||||
color: var(--c-text);
|
||||
text-align: center;
|
||||
|
||||
margin: auto;
|
||||
@@ -52,7 +52,7 @@ nav a{
|
||||
text-decoration: none;
|
||||
}
|
||||
nav a:hover{
|
||||
color: #fff;
|
||||
color: var(--c-text);
|
||||
}
|
||||
/* Unify link/summary layout to prevent distortion */
|
||||
nav a,
|
||||
@@ -75,7 +75,7 @@ nav a::before {
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
background-color: var(--c-other);
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ nav a:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
nav summary:hover {
|
||||
color: #fff;
|
||||
color: var(--c-text);
|
||||
}
|
||||
|
||||
/* underline effect shared for links and summary */
|
||||
@@ -96,7 +96,7 @@ nav summary::before {
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
background-color: var(--c-other);
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ export default function HomeNav() {
|
||||
|
||||
return (
|
||||
<nav className={styles.nav}>
|
||||
<FaBars
|
||||
className={`${styles.toggle} ${navOpen ? styles.toggleRotated : ""}`}
|
||||
<div
|
||||
className={`inline-flex items-center justify-center w-12 h-12 rounded-xl bg-brandGradient text-white shadow-glow cursor-pointer ${styles.toggle} ${navOpen ? styles.toggleRotated : ""}`}
|
||||
onClick={toggleNav}
|
||||
aria-label="Toggle navigation"
|
||||
aria-expanded={navOpen}
|
||||
/>
|
||||
>
|
||||
<FaBars />
|
||||
</div>
|
||||
|
||||
<ul className={`${styles.navList} ${navOpen ? styles.open : ""}`}>
|
||||
<li id="nav-logo" className={styles.logo}>
|
||||
@@ -30,7 +32,9 @@ export default function HomeNav() {
|
||||
<details>
|
||||
<summary>
|
||||
Services
|
||||
<FaChevronDown className={`${styles.caret} ml-2 inline-block`} aria-hidden="true" />
|
||||
<span className="inline-flex items-center justify-center w-6 h-6 rounded-md bg-brandGradient text-white ml-2 shadow-glow">
|
||||
<FaChevronDown className={styles.caret} aria-hidden="true" />
|
||||
</span>
|
||||
</summary>
|
||||
<ul className={styles.submenu}>
|
||||
<li><a href="#web">Web development</a></li>
|
||||
|
||||
@@ -16,19 +16,19 @@ export default function SiteNav() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<header className={`sticky top-0 z-50 transition-all ${scrolled ? 'bg-slate-800/95 backdrop-blur-md shadow-lg' : 'bg-transparent'}`}>
|
||||
<nav className="relative container mx-auto px-4 flex items-center justify-between h-16 text-[var(--c-text)] font-medium">
|
||||
<header className={`sticky top-0 z-50 transition-all ${scrolled ? 'bg-brand-bg/95 backdrop-blur-md shadow-lg' : 'bg-transparent'}`}>
|
||||
<nav className="relative container mx-auto px-4 flex items-center justify-between h-16 text-brand-text font-medium">
|
||||
<div className="text-xl tracking-wide font-semibold">
|
||||
<NavLink to="/" className="inline-block px-2 py-1 rounded nav-item text-rainbow font-bold">vontor.cz</NavLink>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Toggle navigation"
|
||||
onClick={() => setOpen(o => !o)}
|
||||
className="md:hidden p-2 rounded glow focus:outline-none"
|
||||
className="md:hidden p-2 rounded-xl glow focus:outline-none bg-brandGradient text-white shadow-glow"
|
||||
>
|
||||
{open ? <FaTimes /> : <FaBars />}
|
||||
</button>
|
||||
<ul className={`md:flex md:items-center md:gap-8 md:static absolute left-0 w-full md:w-auto transition-all duration-300 ${open ? 'top-16 bg-slate-800/95 pb-6 rounded-lg backdrop-blur-md' : 'top-[-500px]'}`}>
|
||||
<ul className={`md:flex md:items-center md:gap-8 md:static absolute left-0 w-full md:w-auto transition-all duration-300 ${open ? 'top-16 bg-brand-bg/95 pb-6 rounded-lg backdrop-blur-md' : 'top-[-500px]'}`}>
|
||||
<li className="flex"><NavLink to="/" onClick={()=>setOpen(false)} className={linkCls}>Home</NavLink></li>
|
||||
<li className="flex"><NavLink to="/portfolio" onClick={()=>setOpen(false)} className={linkCls}>Portfolio</NavLink></li>
|
||||
<li className="flex"><NavLink to="/skills" onClick={()=>setOpen(false)} className={linkCls}>Skills</NavLink></li>
|
||||
@@ -42,7 +42,7 @@ export default function SiteNav() {
|
||||
className={`nav-item px-3 py-2 flex items-center gap-1`}
|
||||
aria-haspopup="true" aria-expanded={servicesOpen}
|
||||
>
|
||||
More <FaChevronDown className={`transition-transform ${servicesOpen ? 'rotate-180' : ''}`} />
|
||||
<span className="inline-flex items-center gap-1">More <span className="inline-flex items-center justify-center w-6 h-6 rounded-md bg-brandGradient text-white"><FaChevronDown className={`transition-transform ${servicesOpen ? 'rotate-180' : ''}`} /></span></span>
|
||||
</button>
|
||||
{/* Mobile inline dropdown */}
|
||||
<div className={`md:hidden w-full mt-2 ${servicesOpen ? 'block' : 'hidden'}`}>
|
||||
@@ -69,5 +69,5 @@ export default function SiteNav() {
|
||||
);
|
||||
}
|
||||
|
||||
const linkCls = ({ isActive }: { isActive: boolean }) => `nav-item px-3 py-2 rounded transition-colors ${isActive ? 'active text-[var(--c-other)] font-semibold' : 'hover:text-[var(--c-other)]'}`;
|
||||
const linkCls = ({ isActive }: { isActive: boolean }) => `nav-item px-3 py-2 rounded transition-colors ${isActive ? 'active text-brand-accent font-semibold' : 'hover:text-brand-accent'}`;
|
||||
const dropdownCls = "block px-2 py-1 rounded hover:bg-[color-mix(in_hsl,var(--c-other),transparent_85%)]";
|
||||
@@ -8,11 +8,11 @@ const categories: { name: string; items: string[] }[] = [
|
||||
|
||||
export default function SkillsSection() {
|
||||
return (
|
||||
<section id="skills" className="py-20 bg-gradient-to-b from-slate-900 to-slate-800">
|
||||
<section id="skills" className="py-20 bg-gradient-to-b from-brand-bg to-brand-bgLight">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-4xl font-bold mb-4 text-rainbow">Skills</h2>
|
||||
<p className="text-gray-300 max-w-2xl mx-auto">Core technologies and tools I use across backend, frontend, infrastructure and hardware.</p>
|
||||
<p className="text-brand-text/80 max-w-2xl mx-auto">Core technologies and tools I use across backend, frontend, infrastructure and hardware.</p>
|
||||
</div>
|
||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{categories.map(cat => (
|
||||
@@ -20,9 +20,9 @@ export default function SkillsSection() {
|
||||
<h3 className="font-semibold mb-4 text-rainbow tracking-wide">{cat.name}</h3>
|
||||
<ul className="space-y-2 text-sm">
|
||||
{cat.items.map(i => (
|
||||
<li key={i} className="flex items-center gap-2 text-gray-300">
|
||||
<span className="inline-block w-2 h-2 rounded-full bg-fuchsia-600" />
|
||||
<span className="group-hover:text-white transition-colors">{i}</span>
|
||||
<li key={i} className="flex items-center gap-2 text-brand-text/80">
|
||||
<span className="inline-block w-2 h-2 rounded-full bg-brand-accent" />
|
||||
<span className="group-hover:text-brand-text transition-colors">{i}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
export default function TradingGraph() {
|
||||
return (
|
||||
<section id="live" className="py-20 bg-gradient-to-b from-slate-900 to-slate-800">
|
||||
<section id="live" className="py-20 bg-gradient-to-b from-brand-bg to-brand-bgLight">
|
||||
<div className="container mx-auto px-4">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6 text-rainbow">Live Performance</h2>
|
||||
<div className="card p-4 md:p-6">
|
||||
<div className="mb-3 text-sm text-gray-400">Trading212 graph placeholder</div>
|
||||
<div className="aspect-[16/9] w-full rounded border border-slate-700 bg-black/40 grid place-items-center">
|
||||
<span className="text-gray-400">Graph will appear here</span>
|
||||
<div className="mb-3 text-sm text-brand-text/60">Trading212 graph placeholder</div>
|
||||
<div className="aspect-[16/9] w-full rounded border border-brand-lines/50 bg-brand-bg/40 grid place-items-center">
|
||||
<span className="text-brand-text/60">Graph will appear here</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,8 +28,8 @@ body {
|
||||
font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
|
||||
color: var(--c-text);
|
||||
background: radial-gradient(1200px 800px at 10% 10%, var(--c-background-light), transparent 60%),
|
||||
radial-gradient(1000px 700px at 90% 20%, rgba(135,169,218,0.15), transparent 60%),
|
||||
linear-gradient(180deg, var(--c-background) 0%, #02162f 100%);
|
||||
radial-gradient(1000px 700px at 90% 20%, color-mix(in hsl, var(--c-lines), transparent 85%), transparent 60%),
|
||||
linear-gradient(180deg, var(--c-background) 0%, color-mix(in hsl, var(--c-background), black 15%) 100%);
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -157,3 +157,20 @@ button:active { transform: translateY(1px) }
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Use project palette for list markers (bullets) and summary markers */
|
||||
li::marker,
|
||||
ol li::marker,
|
||||
ul li::marker,
|
||||
summary::marker {
|
||||
color: var(--c-boxes);
|
||||
}
|
||||
|
||||
/* Ensure fallback for older browsers by tinting bullets via ::before where used */
|
||||
li.custom-marker::before {
|
||||
content: "\2022"; /* bullet */
|
||||
color: var(--c-boxes);
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
margin-left: -1em;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,35 @@
|
||||
import ContactMeForm from "../../components/Forms/ContactMe/ContactMeForm";
|
||||
|
||||
export default function ContactPage(){
|
||||
return (
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<h2 className="text-2xl md:text-3xl font-bold mb-4">Get in Touch</h2>
|
||||
<p className="text-[var(--c-lines)] mb-6">Reach out via email or phone. Social links are in the footer.</p>
|
||||
<div className="glass p-6 max-w-lg">
|
||||
<h2 className="text-2xl md:text-3xl font-bold mb-2 text-rainbow">Get in Touch</h2>
|
||||
<p className="text-brand-lines mb-6">Reach out via the form or use the details below.</p>
|
||||
|
||||
{/* Desktop/tablet: envelope animation + slide-out form */}
|
||||
<div className="hidden md:block">
|
||||
<ContactMeForm />
|
||||
</div>
|
||||
|
||||
{/* Mobile: simple card version without envelope */}
|
||||
<div className="md:hidden">
|
||||
<div className="card p-5">
|
||||
<form className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm text-brand-lines mb-1">Your email</label>
|
||||
<input type="email" required className="w-full rounded-lg bg-brand-bgLight/40 border border-brand-lines/40 focus:outline-none focus:ring-2 focus:ring-brand-accent px-3 py-2 text-brand-text placeholder:text-brand-lines/70" placeholder="Enter your email" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm text-brand-lines mb-1">Message</label>
|
||||
<textarea required rows={5} className="w-full rounded-lg bg-brand-bgLight/40 border border-brand-lines/40 focus:outline-none focus:ring-2 focus:ring-brand-accent px-3 py-2 text-brand-text placeholder:text-brand-lines/70" placeholder="How can I help?" />
|
||||
</div>
|
||||
<button type="submit" className="px-4 py-2 rounded-lg font-semibold text-white bg-brandGradient shadow-glow">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="glass p-6 max-w-lg mt-8">
|
||||
<p><strong>Email:</strong> <a href="mailto:brunovontor@gmail.com" className="hover:text-[var(--c-other)]">brunovontor@gmail.com</a></p>
|
||||
<p className="mt-2"><strong>Phone:</strong> <a href="tel:+420605512624" className="hover:text-[var(--c-other)]">+420 605 512 624</a></p>
|
||||
</div>
|
||||
|
||||
@@ -109,7 +109,7 @@ export default function Downloader() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!url || probing}
|
||||
className="px-3 py-2 rounded bg-blue-600 text-white disabled:opacity-50"
|
||||
className="px-3 py-2 rounded bg-brand-accent text-brand-text disabled:opacity-50"
|
||||
>
|
||||
{probing ? "Probing..." : "Get info"}
|
||||
</button>
|
||||
@@ -117,7 +117,7 @@ export default function Downloader() {
|
||||
type="button"
|
||||
onClick={onDownload}
|
||||
disabled={!canDownload || downloading}
|
||||
className="px-3 py-2 rounded bg-emerald-600 text-white disabled:opacity-50"
|
||||
className="px-3 py-2 rounded bg-brand-accent text-brand-text disabled:opacity-50"
|
||||
>
|
||||
{downloading ? "Downloading..." : "Download"}
|
||||
</button>
|
||||
@@ -134,7 +134,7 @@ export default function Downloader() {
|
||||
className="w-40 h-24 object-cover rounded border"
|
||||
/>
|
||||
)}
|
||||
<div className="text-sm text-gray-800 space-y-1">
|
||||
<div className="text-sm text-brand-text/90 space-y-1">
|
||||
<div>
|
||||
<span className="font-medium">Title:</span> {info.title || "-"}
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.introduction article header {}
|
||||
/* header rules intentionally left out; keep markup clean */
|
||||
|
||||
.introduction article:nth-child(1) {
|
||||
width: 100%;
|
||||
@@ -71,7 +71,9 @@
|
||||
list-style: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgba(255, 255, 255, 35%);
|
||||
/* use design palette for animated dots */
|
||||
background: var(--c-boxes);
|
||||
opacity: 0.35;
|
||||
animation: animate 4s linear infinite;
|
||||
bottom: -150px;
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ export default {
|
||||
fontFamily: {
|
||||
inter: ['Inter', 'ui-sans-serif', 'system-ui'],
|
||||
},
|
||||
backgroundImage: {
|
||||
brandGradient: 'linear-gradient(135deg, var(--c-boxes), var(--c-other))',
|
||||
brandGlass: 'linear-gradient(180deg, color-mix(in hsl, var(--c-background-light), transparent 30%), color-mix(in hsl, black, transparent 70%))'
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
bg: 'var(--c-background)',
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
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 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="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
<ToastContainer
|
||||
position="top-right"
|
||||
autoClose={3000}
|
||||
newestOnTop
|
||||
closeOnClick
|
||||
pauseOnHover
|
||||
theme="dark"
|
||||
/>
|
||||
</main>
|
||||
</Router>
|
||||
</Theme>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,26 +0,0 @@
|
||||
import js from '@eslint/js'
|
||||
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'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
@@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" href="https://meku.dev/favicon.ico" />
|
||||
<title>Vontor.cz Creative Portfolio Website</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<meta name="generator" content="Meku" />
|
||||
<meta name="description" content="Vontor.cz Creative Portfolio Website Generated with Meku" />
|
||||
<meta name="author" content="Meku" />
|
||||
|
||||
<meta property="og:title" content="Vontor.cz Creative Portfolio Website" />
|
||||
<meta property="og:description" content="Vontor.cz Creative Portfolio Website 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="/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,50 +0,0 @@
|
||||
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>
|
||||
);
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "vontor.cz-creative-portfolio-website",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"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-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.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",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.43.0"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Allow: /
|
||||
|
||||
User-agent: Twitterbot
|
||||
Allow: /
|
||||
|
||||
User-agent: facebookexternalhit
|
||||
Allow: /
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
@@ -1,244 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Mail, Phone, MapPin, Send, CheckCircle, AlertCircle } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const contactSchema = z.object({
|
||||
name: z.string().min(2, 'Name must be at least 2 characters'),
|
||||
email: z.string().email('Please enter a valid email address'),
|
||||
subject: z.string().min(5, 'Subject must be at least 5 characters'),
|
||||
message: z.string().min(10, 'Message must be at least 10 characters')
|
||||
});
|
||||
|
||||
type ContactFormData = z.infer<typeof contactSchema>;
|
||||
|
||||
const Contact: React.FC = () => {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors }
|
||||
} = useForm<ContactFormData>({
|
||||
resolver: zodResolver(contactSchema)
|
||||
});
|
||||
|
||||
const onSubmit = async (data: ContactFormData) => {
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
// Simulate form submission
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
console.log('Form submitted:', data);
|
||||
toast.success('Message sent successfully! I\'ll get back to you soon.');
|
||||
reset();
|
||||
} catch (error) {
|
||||
toast.error('Failed to send message. Please try again.');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const contactInfo = [
|
||||
{
|
||||
icon: <Mail size={24} />,
|
||||
title: 'Email',
|
||||
value: 'hello@vontor.cz',
|
||||
link: 'mailto:hello@vontor.cz'
|
||||
},
|
||||
{
|
||||
icon: <Phone size={24} />,
|
||||
title: 'Phone',
|
||||
value: '+420 XXX XXX XXX',
|
||||
link: 'tel:+420XXXXXXXXX'
|
||||
},
|
||||
{
|
||||
icon: <MapPin size={24} />,
|
||||
title: 'Location',
|
||||
value: 'Czech Republic',
|
||||
link: null
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="contact" className="py-20 bg-gradient-to-b from-slate-900 to-black">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl lg:text-5xl font-bold mb-6">
|
||||
<span className="bg-gradient-to-r from-fuchsia-600 to-orange-500 bg-clip-text text-transparent">
|
||||
Get In Touch
|
||||
</span>
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-3xl mx-auto">
|
||||
Ready to bring your ideas to life? Let's discuss your next project and create something amazing together.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="grid lg:grid-cols-2 gap-12">
|
||||
|
||||
{/* Contact Information */}
|
||||
<div>
|
||||
<h3 className="text-3xl font-bold text-white mb-8">Let's Connect</h3>
|
||||
<p className="text-gray-300 text-lg mb-8 leading-relaxed">
|
||||
I'm always excited to work on new projects and collaborate with creative minds.
|
||||
Whether you need a website, web application, or technical consultation,
|
||||
I'm here to help turn your vision into reality.
|
||||
</p>
|
||||
|
||||
{/* Contact Info Cards */}
|
||||
<div className="space-y-6 mb-8">
|
||||
{contactInfo.map((info, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center gap-4 p-4 bg-slate-800 rounded-xl border border-slate-700 hover:border-violet-400/50 transition-all duration-300"
|
||||
>
|
||||
<div className="p-3 bg-gradient-to-r from-fuchsia-600 to-orange-500 rounded-lg">
|
||||
<div className="text-white">
|
||||
{info.icon}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-white font-semibold mb-1">{info.title}</h4>
|
||||
{info.link ? (
|
||||
<a
|
||||
href={info.link}
|
||||
className="text-gray-300 hover:text-fuchsia-600 transition-colors duration-300"
|
||||
>
|
||||
{info.value}
|
||||
</a>
|
||||
) : (
|
||||
<span className="text-gray-300">{info.value}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Availability Status */}
|
||||
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<CheckCircle className="text-green-500" size={24} />
|
||||
<h4 className="text-white font-semibold">Currently Available</h4>
|
||||
</div>
|
||||
<p className="text-gray-300 text-sm">
|
||||
I'm currently accepting new projects and collaborations.
|
||||
Typical response time is within 24 hours.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Form */}
|
||||
<div className="bg-slate-800 rounded-2xl p-8 border border-slate-700">
|
||||
<h3 className="text-2xl font-bold text-white mb-6">Send a Message</h3>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-white font-medium mb-2">
|
||||
Name *
|
||||
</label>
|
||||
<input
|
||||
{...register('name')}
|
||||
type="text"
|
||||
id="name"
|
||||
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-gray-400 focus:border-violet-400 focus:outline-none transition-colors duration-300"
|
||||
placeholder="Your name"
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className="text-red-400 text-sm mt-1 flex items-center gap-1">
|
||||
<AlertCircle size={16} />
|
||||
{errors.name.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-white font-medium mb-2">
|
||||
Email *
|
||||
</label>
|
||||
<input
|
||||
{...register('email')}
|
||||
type="email"
|
||||
id="email"
|
||||
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-gray-400 focus:border-violet-400 focus:outline-none transition-colors duration-300"
|
||||
placeholder="your@email.com"
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="text-red-400 text-sm mt-1 flex items-center gap-1">
|
||||
<AlertCircle size={16} />
|
||||
{errors.email.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="subject" className="block text-white font-medium mb-2">
|
||||
Subject *
|
||||
</label>
|
||||
<input
|
||||
{...register('subject')}
|
||||
type="text"
|
||||
id="subject"
|
||||
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-gray-400 focus:border-violet-400 focus:outline-none transition-colors duration-300"
|
||||
placeholder="Project inquiry, collaboration, etc."
|
||||
/>
|
||||
{errors.subject && (
|
||||
<p className="text-red-400 text-sm mt-1 flex items-center gap-1">
|
||||
<AlertCircle size={16} />
|
||||
{errors.subject.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-white font-medium mb-2">
|
||||
Message *
|
||||
</label>
|
||||
<textarea
|
||||
{...register('message')}
|
||||
id="message"
|
||||
rows={6}
|
||||
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-gray-400 focus:border-violet-400 focus:outline-none transition-colors duration-300 resize-vertical"
|
||||
placeholder="Tell me about your project, timeline, budget, and any specific requirements..."
|
||||
/>
|
||||
{errors.message && (
|
||||
<p className="text-red-400 text-sm mt-1 flex items-center gap-1">
|
||||
<AlertCircle size={16} />
|
||||
{errors.message.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="w-full flex items-center justify-center gap-3 px-8 py-4 bg-gradient-to-r from-fuchsia-600 to-orange-500 text-white font-semibold rounded-lg hover:shadow-lg hover:shadow-fuchsia-600/25 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin"></div>
|
||||
Sending...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send size={20} />
|
||||
Send Message
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
@@ -1,178 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Coffee, Zap, Battery, Heart, Gift } from 'lucide-react';
|
||||
|
||||
interface DonationTier {
|
||||
id: string;
|
||||
name: string;
|
||||
price: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
features: string[];
|
||||
popular?: boolean;
|
||||
}
|
||||
|
||||
const DonateShop: React.FC = () => {
|
||||
const donationTiers: DonationTier[] = [
|
||||
{
|
||||
id: 'coffee',
|
||||
name: 'Buy Me a Coffee',
|
||||
price: '$5',
|
||||
description: 'Fuel my coding sessions with caffeine',
|
||||
icon: <Coffee size={32} />,
|
||||
features: [
|
||||
'Support ongoing projects',
|
||||
'Keep me caffeinated',
|
||||
'Thank you message',
|
||||
'Good karma points'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'gpu',
|
||||
name: 'GPU Upgrade Fund',
|
||||
price: '$50',
|
||||
description: 'Help upgrade my development setup',
|
||||
icon: <Zap size={32} />,
|
||||
features: [
|
||||
'Better performance',
|
||||
'Faster rendering',
|
||||
'Enhanced productivity',
|
||||
'Priority support',
|
||||
'Exclusive updates'
|
||||
],
|
||||
popular: true
|
||||
},
|
||||
{
|
||||
id: 'drone',
|
||||
name: 'Drone Battery Pack',
|
||||
price: '$25',
|
||||
description: 'Extended flight time for aerial photography',
|
||||
icon: <Battery size={32} />,
|
||||
features: [
|
||||
'Longer flight sessions',
|
||||
'More aerial content',
|
||||
'Better coverage',
|
||||
'Behind-the-scenes access'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const handleDonate = (tierId: string) => {
|
||||
// Placeholder for donation handling
|
||||
console.log(`Donate to tier: ${tierId}`);
|
||||
// In a real implementation, this would integrate with a payment processor
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="donate" className="py-20 bg-gradient-to-b from-slate-800 to-slate-900">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-16">
|
||||
<div className="flex items-center justify-center gap-3 mb-6">
|
||||
<Heart className="text-fuchsia-600" size={40} />
|
||||
<h2 className="text-4xl lg:text-5xl font-bold">
|
||||
<span className="bg-gradient-to-r from-fuchsia-600 to-orange-500 bg-clip-text text-transparent">
|
||||
Support My Work
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-xl text-gray-300 max-w-3xl mx-auto">
|
||||
Your support helps me create better content, upgrade equipment, and continue
|
||||
developing innovative projects. Every contribution makes a difference!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Donation Tiers */}
|
||||
<div className="grid md:grid-cols-3 gap-8 max-w-6xl mx-auto mb-16">
|
||||
{donationTiers.map((tier) => (
|
||||
<div
|
||||
key={tier.id}
|
||||
className={`relative bg-slate-800 rounded-2xl p-8 shadow-xl border transition-all duration-300 hover:transform hover:scale-105 ${
|
||||
tier.popular
|
||||
? 'border-fuchsia-600 shadow-fuchsia-600/20'
|
||||
: 'border-slate-700 hover:border-violet-400/50'
|
||||
}`}
|
||||
>
|
||||
{tier.popular && (
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2">
|
||||
<span className="bg-gradient-to-r from-fuchsia-600 to-orange-500 text-white px-4 py-2 rounded-full text-sm font-semibold">
|
||||
Most Popular
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="text-center mb-6">
|
||||
<div className={`inline-flex p-4 rounded-2xl mb-4 ${
|
||||
tier.popular
|
||||
? 'bg-gradient-to-r from-fuchsia-600 to-orange-500'
|
||||
: 'bg-slate-700'
|
||||
}`}>
|
||||
<div className="text-white">
|
||||
{tier.icon}
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-white mb-2">{tier.name}</h3>
|
||||
<div className="text-4xl font-bold mb-2">
|
||||
<span className="bg-gradient-to-r from-fuchsia-600 to-orange-500 bg-clip-text text-transparent">
|
||||
{tier.price}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-400">{tier.description}</p>
|
||||
</div>
|
||||
|
||||
<ul className="space-y-3 mb-8">
|
||||
{tier.features.map((feature, index) => (
|
||||
<li key={index} className="flex items-center gap-3 text-gray-300">
|
||||
<div className="w-2 h-2 bg-violet-400 rounded-full flex-shrink-0"></div>
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
onClick={() => handleDonate(tier.id)}
|
||||
className={`w-full py-4 rounded-xl font-semibold transition-all duration-300 ${
|
||||
tier.popular
|
||||
? 'bg-gradient-to-r from-fuchsia-600 to-orange-500 text-white hover:shadow-lg hover:shadow-fuchsia-600/25'
|
||||
: 'bg-slate-700 text-white hover:bg-slate-600 border border-slate-600 hover:border-violet-400'
|
||||
}`}
|
||||
>
|
||||
Donate {tier.price}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Custom Amount Section */}
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="bg-slate-800 rounded-2xl p-8 border border-slate-700 text-center">
|
||||
<Gift className="text-violet-400 mx-auto mb-4" size={48} />
|
||||
<h3 className="text-2xl font-bold text-white mb-4">Custom Amount</h3>
|
||||
<p className="text-gray-300 mb-6">
|
||||
Want to contribute a different amount? You can choose any amount that feels right for you.
|
||||
</p>
|
||||
<div className="flex gap-4 max-w-md mx-auto">
|
||||
<input
|
||||
type="number"
|
||||
placeholder="Enter amount"
|
||||
className="flex-1 px-4 py-3 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-gray-400 focus:border-violet-400 focus:outline-none"
|
||||
/>
|
||||
<button className="px-8 py-3 bg-gradient-to-r from-fuchsia-600 to-orange-500 text-white font-semibold rounded-lg hover:shadow-lg hover:shadow-fuchsia-600/25 transition-all duration-300">
|
||||
Donate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Thank You Message */}
|
||||
<div className="text-center mt-12">
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
Thank you for considering supporting my work! Your contributions help me continue creating
|
||||
innovative projects and sharing knowledge with the community. Every donation, no matter the size,
|
||||
is deeply appreciated. ❤️
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default DonateShop;
|
||||
@@ -1,160 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Instagram, Twitter, Youtube, Github, Linkedin, Gamepad2, Mail, MapPin, Phone } from 'lucide-react';
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
const socialLinks = [
|
||||
{ name: 'Instagram', icon: <Instagram size={20} />, url: 'https://instagram.com', color: 'hover:text-pink-500' },
|
||||
{ name: 'Twitter/X', icon: <Twitter size={20} />, url: 'https://twitter.com', color: 'hover:text-blue-400' },
|
||||
{ name: 'YouTube', icon: <Youtube size={20} />, url: 'https://youtube.com', color: 'hover:text-red-500' },
|
||||
{ name: 'GitHub', icon: <Github size={20} />, url: 'https://github.com', color: 'hover:text-gray-300' },
|
||||
{ name: 'LinkedIn', icon: <Linkedin size={20} />, url: 'https://linkedin.com', color: 'hover:text-blue-600' },
|
||||
{ name: 'Steam', icon: <Gamepad2 size={20} />, url: 'https://steamcommunity.com', color: 'hover:text-blue-500' }
|
||||
];
|
||||
|
||||
const quickLinks = [
|
||||
{ name: 'Home', href: '#home' },
|
||||
{ name: 'Portfolio', href: '#portfolio' },
|
||||
{ name: 'Skills', href: '#skills' },
|
||||
{ name: 'Donate', href: '#donate' },
|
||||
{ name: 'Contact', href: '#contact' }
|
||||
];
|
||||
|
||||
const legalLinks = [
|
||||
{ name: 'Privacy Policy', href: '#' },
|
||||
{ name: 'Terms of Service', href: '#' },
|
||||
{ name: 'Cookie Policy', href: '#' }
|
||||
];
|
||||
|
||||
const scrollToSection = (href: string) => {
|
||||
const element = document.querySelector(href);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<footer className="bg-slate-900 border-t border-slate-800">
|
||||
<div className="container mx-auto px-4 py-16">
|
||||
<div className="grid lg:grid-cols-4 md:grid-cols-2 gap-8">
|
||||
|
||||
{/* Brand Section */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="text-3xl font-bold bg-gradient-to-r from-fuchsia-600 to-orange-500 bg-clip-text text-transparent mb-4">
|
||||
Vontor.cz
|
||||
</div>
|
||||
<p className="text-gray-400 mb-6 leading-relaxed">
|
||||
Creative tech solutions and innovative design by Bruno Vontor.
|
||||
Bringing ideas to life through code, creativity, and cutting-edge technology.
|
||||
</p>
|
||||
|
||||
{/* Social Media Links */}
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{socialLinks.map((social) => (
|
||||
<a
|
||||
key={social.name}
|
||||
href={social.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`p-3 bg-slate-800 rounded-lg border border-slate-700 text-gray-400 transition-all duration-300 hover:border-violet-400/50 hover:transform hover:scale-110 ${social.color}`}
|
||||
aria-label={social.name}
|
||||
>
|
||||
{social.icon}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-white mb-6">Quick Links</h3>
|
||||
<ul className="space-y-3">
|
||||
{quickLinks.map((link) => (
|
||||
<li key={link.name}>
|
||||
<button
|
||||
onClick={() => scrollToSection(link.href)}
|
||||
className="text-gray-400 hover:text-fuchsia-600 transition-colors duration-300"
|
||||
>
|
||||
{link.name}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-white mb-6">Contact Info</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3 text-gray-400">
|
||||
<Mail size={18} className="text-fuchsia-600" />
|
||||
<span>hello@vontor.cz</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-gray-400">
|
||||
<MapPin size={18} className="text-fuchsia-600" />
|
||||
<span>Czech Republic</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-gray-400">
|
||||
<Phone size={18} className="text-fuchsia-600" />
|
||||
<span>Available on request</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Newsletter */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-white mb-6">Stay Updated</h3>
|
||||
<p className="text-gray-400 mb-4">
|
||||
Subscribe to get notified about new projects and updates.
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
className="flex-1 px-4 py-3 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-400 focus:border-violet-400 focus:outline-none"
|
||||
/>
|
||||
<button className="px-6 py-3 bg-gradient-to-r from-fuchsia-600 to-orange-500 text-white font-semibold rounded-lg hover:shadow-lg hover:shadow-fuchsia-600/25 transition-all duration-300">
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Section */}
|
||||
<div className="border-t border-slate-800 mt-12 pt-8">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
|
||||
{/* Copyright */}
|
||||
<div className="text-gray-400 text-center md:text-left">
|
||||
<p>
|
||||
© 2025 Vontor.cz - All rights reserved. Built with ❤️ by{' '}
|
||||
<a
|
||||
href="https://meku.dev"
|
||||
target="_blank"
|
||||
rel="nofollow noopener noreferrer"
|
||||
className="text-fuchsia-600 hover:text-orange-500 transition-colors duration-300"
|
||||
>
|
||||
Meku.dev
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Legal Links */}
|
||||
<div className="flex flex-wrap gap-6">
|
||||
{legalLinks.map((link) => (
|
||||
<a
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
className="text-gray-400 hover:text-fuchsia-600 transition-colors duration-300 text-sm"
|
||||
>
|
||||
{link.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
@@ -1,84 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Menu, X } from 'lucide-react';
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 50);
|
||||
};
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
const navItems = [
|
||||
{ name: 'Home', href: '#home' },
|
||||
{ name: 'Portfolio', href: '#portfolio' },
|
||||
{ name: 'Skills', href: '#skills' },
|
||||
{ name: 'Donate/Shop', href: '#donate' },
|
||||
{ name: 'Contact', href: '#contact' }
|
||||
];
|
||||
|
||||
const scrollToSection = (href: string) => {
|
||||
const element = document.querySelector(href);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
setIsMenuOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
|
||||
isScrolled ? 'bg-slate-800/95 backdrop-blur-md shadow-lg' : 'bg-transparent'
|
||||
}`}>
|
||||
<nav className="container mx-auto px-4 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-2xl font-bold bg-gradient-to-r from-fuchsia-600 to-orange-500 bg-clip-text text-transparent">
|
||||
Vontor.cz
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden md:flex space-x-8">
|
||||
{navItems.map((item) => (
|
||||
<button
|
||||
key={item.name}
|
||||
onClick={() => scrollToSection(item.href)}
|
||||
className="text-white hover:text-fuchsia-600 transition-colors duration-300 font-medium"
|
||||
>
|
||||
{item.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
className="md:hidden text-white hover:text-fuchsia-600 transition-colors"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation */}
|
||||
{isMenuOpen && (
|
||||
<div className="md:hidden mt-4 py-4 bg-slate-800/95 rounded-lg backdrop-blur-md">
|
||||
{navItems.map((item) => (
|
||||
<button
|
||||
key={item.name}
|
||||
onClick={() => scrollToSection(item.href)}
|
||||
className="block w-full text-left px-4 py-2 text-white hover:text-fuchsia-600 hover:bg-slate-700/50 transition-all duration-300"
|
||||
>
|
||||
{item.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -1,144 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ChevronLeft, ChevronRight, Play } from 'lucide-react';
|
||||
|
||||
const Hero: React.FC = () => {
|
||||
const [currentSlide, setCurrentSlide] = useState(0);
|
||||
|
||||
const slides = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Creative Tech Solutions",
|
||||
subtitle: "Innovative development meets stunning design",
|
||||
videoId: "dQw4w9WgXcQ" // Placeholder YouTube ID
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Drone Photography",
|
||||
subtitle: "Capturing perspectives from above",
|
||||
videoId: "dQw4w9WgXcQ" // Placeholder YouTube ID
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Full-Stack Development",
|
||||
subtitle: "Building the future, one line at a time",
|
||||
videoId: "dQw4w9WgXcQ" // Placeholder YouTube ID
|
||||
}
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setCurrentSlide((prev) => (prev + 1) % slides.length);
|
||||
}, 8000);
|
||||
return () => clearInterval(timer);
|
||||
}, [slides.length]);
|
||||
|
||||
const nextSlide = () => {
|
||||
setCurrentSlide((prev) => (prev + 1) % slides.length);
|
||||
};
|
||||
|
||||
const prevSlide = () => {
|
||||
setCurrentSlide((prev) => (prev - 1 + slides.length) % slides.length);
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="home" className="relative min-h-screen flex items-center justify-center overflow-hidden">
|
||||
{/* Background Gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-slate-800 via-slate-900 to-black"></div>
|
||||
|
||||
{/* Animated Background Elements */}
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-fuchsia-600/10 rounded-full blur-3xl animate-pulse"></div>
|
||||
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-orange-500/10 rounded-full blur-3xl animate-pulse delay-1000"></div>
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-80 h-80 bg-violet-400/10 rounded-full blur-3xl animate-pulse delay-2000"></div>
|
||||
</div>
|
||||
|
||||
{/* Content Container */}
|
||||
<div className="relative z-10 container mx-auto px-4 py-20">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
|
||||
{/* Text Content */}
|
||||
<div className="text-center lg:text-left">
|
||||
<h1 className="text-5xl lg:text-7xl font-bold mb-6 leading-tight">
|
||||
<span className="bg-gradient-to-r from-fuchsia-600 via-orange-500 to-violet-400 bg-clip-text text-transparent">
|
||||
Welcome to
|
||||
</span>
|
||||
<br />
|
||||
<span className="text-white">Vontor.cz</span>
|
||||
</h1>
|
||||
<p className="text-xl lg:text-2xl text-gray-300 mb-8 leading-relaxed">
|
||||
Creative Tech & Design by <span className="text-fuchsia-600 font-semibold">Bruno Vontor</span>
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
|
||||
<button
|
||||
onClick={() => document.querySelector('#portfolio')?.scrollIntoView({ behavior: 'smooth' })}
|
||||
className="px-8 py-4 bg-gradient-to-r from-fuchsia-600 to-orange-500 text-white font-semibold rounded-lg hover:shadow-lg hover:shadow-fuchsia-600/25 transition-all duration-300 transform hover:scale-105"
|
||||
>
|
||||
View Portfolio
|
||||
</button>
|
||||
<button
|
||||
onClick={() => document.querySelector('#contact')?.scrollIntoView({ behavior: 'smooth' })}
|
||||
className="px-8 py-4 border-2 border-violet-400 text-violet-400 font-semibold rounded-lg hover:bg-violet-400 hover:text-white transition-all duration-300"
|
||||
>
|
||||
Get In Touch
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Video Carousel */}
|
||||
<div className="relative">
|
||||
<div className="relative aspect-video bg-slate-800 rounded-xl overflow-hidden shadow-2xl">
|
||||
<iframe
|
||||
src={`https://www.youtube.com/embed/${slides[currentSlide].videoId}?autoplay=1&mute=1&loop=1&playlist=${slides[currentSlide].videoId}`}
|
||||
title={slides[currentSlide].title}
|
||||
className="w-full h-full"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
|
||||
{/* Overlay */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none"></div>
|
||||
|
||||
{/* Slide Info */}
|
||||
<div className="absolute bottom-4 left-4 text-white">
|
||||
<h3 className="text-lg font-semibold">{slides[currentSlide].title}</h3>
|
||||
<p className="text-sm text-gray-300">{slides[currentSlide].subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation Buttons */}
|
||||
<button
|
||||
onClick={prevSlide}
|
||||
className="absolute left-4 top-1/2 transform -translate-y-1/2 bg-black/50 hover:bg-black/70 text-white p-2 rounded-full transition-all duration-300"
|
||||
aria-label="Previous slide"
|
||||
>
|
||||
<ChevronLeft size={24} />
|
||||
</button>
|
||||
<button
|
||||
onClick={nextSlide}
|
||||
className="absolute right-4 top-1/2 transform -translate-y-1/2 bg-black/50 hover:bg-black/70 text-white p-2 rounded-full transition-all duration-300"
|
||||
aria-label="Next slide"
|
||||
>
|
||||
<ChevronRight size={24} />
|
||||
</button>
|
||||
|
||||
{/* Slide Indicators */}
|
||||
<div className="flex justify-center mt-4 space-x-2">
|
||||
{slides.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setCurrentSlide(index)}
|
||||
className={`w-3 h-3 rounded-full transition-all duration-300 ${
|
||||
index === currentSlide ? 'bg-fuchsia-600' : 'bg-gray-600 hover:bg-gray-400'
|
||||
}`}
|
||||
aria-label={`Go to slide ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Hero;
|
||||
@@ -1,171 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Shield, Server, Lock, Zap, CheckCircle, Globe } from 'lucide-react';
|
||||
|
||||
const HostingSecurity: React.FC = () => {
|
||||
const benefits = [
|
||||
{
|
||||
icon: <Shield size={24} />,
|
||||
title: 'Enhanced Security',
|
||||
description: 'Full control over security configurations, custom firewalls, and regular security updates'
|
||||
},
|
||||
{
|
||||
icon: <Zap size={24} />,
|
||||
title: 'Better Performance',
|
||||
description: 'Optimized server configurations, custom caching, and dedicated resources for faster loading'
|
||||
},
|
||||
{
|
||||
icon: <Lock size={24} />,
|
||||
title: 'Data Privacy',
|
||||
description: 'Complete ownership of your data with no third-party access or data mining concerns'
|
||||
},
|
||||
{
|
||||
icon: <Server size={24} />,
|
||||
title: 'Cost Effective',
|
||||
description: 'Lower long-term costs compared to managed hosting services with better resource allocation'
|
||||
},
|
||||
{
|
||||
icon: <Globe size={24} />,
|
||||
title: 'Custom Domains',
|
||||
description: 'Easy setup of custom domains, subdomains, and SSL certificates for professional presence'
|
||||
},
|
||||
{
|
||||
icon: <CheckCircle size={24} />,
|
||||
title: 'Full Control',
|
||||
description: 'Complete administrative access to configure, customize, and scale your hosting environment'
|
||||
}
|
||||
];
|
||||
|
||||
const techStack = [
|
||||
'Ubuntu Server 22.04 LTS',
|
||||
'Nginx Web Server',
|
||||
'Docker Containerization',
|
||||
'SSL/TLS Encryption',
|
||||
'Automated Backups',
|
||||
'Monitoring & Alerts',
|
||||
'Fail2ban Security',
|
||||
'UFW Firewall'
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-slate-800">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl lg:text-5xl font-bold mb-6">
|
||||
<span className="bg-gradient-to-r from-fuchsia-600 to-orange-500 bg-clip-text text-transparent">
|
||||
Self-Hosting & Security
|
||||
</span>
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-3xl mx-auto">
|
||||
Why I choose self-hosting for better control, security, and performance
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Main Info Panel */}
|
||||
<div className="bg-slate-900 rounded-2xl p-8 mb-12 border border-slate-700">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<h3 className="text-3xl font-bold text-white mb-6">
|
||||
Why Self-Hosting Matters
|
||||
</h3>
|
||||
<p className="text-gray-300 text-lg leading-relaxed mb-6">
|
||||
Self-hosting provides unparalleled control over your digital infrastructure.
|
||||
By managing my own servers, I ensure optimal performance, enhanced security,
|
||||
and complete data ownership while reducing long-term costs.
|
||||
</p>
|
||||
<p className="text-gray-300 leading-relaxed">
|
||||
This approach allows for custom configurations, better resource allocation,
|
||||
and the flexibility to scale applications according to specific needs without
|
||||
vendor lock-in or arbitrary limitations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="bg-slate-800 rounded-xl p-6 border border-slate-600">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Server className="text-fuchsia-600" size={32} />
|
||||
<h4 className="text-xl font-semibold text-white">Server Status</h4>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-300">Uptime</span>
|
||||
<span className="text-green-500 font-semibold">99.9%</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-300">Response Time</span>
|
||||
<span className="text-green-500 font-semibold">< 200ms</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-300">SSL Grade</span>
|
||||
<span className="text-green-500 font-semibold">A+</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-300">Security Score</span>
|
||||
<span className="text-green-500 font-semibold">95/100</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 pt-4 border-t border-slate-600">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<span className="text-green-500 text-sm font-medium">All Systems Operational</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Benefits Grid */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
|
||||
{benefits.map((benefit, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-slate-800 rounded-xl p-6 border border-slate-700 hover:border-violet-400/50 transition-all duration-300 hover:transform hover:scale-105"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 bg-gradient-to-r from-fuchsia-600 to-orange-500 rounded-lg">
|
||||
<div className="text-white">
|
||||
{benefit.icon}
|
||||
</div>
|
||||
</div>
|
||||
<h4 className="text-lg font-semibold text-white">{benefit.title}</h4>
|
||||
</div>
|
||||
<p className="text-gray-300 text-sm leading-relaxed">
|
||||
{benefit.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tech Stack */}
|
||||
<div className="bg-slate-900 rounded-2xl p-8 border border-slate-700">
|
||||
<h3 className="text-2xl font-bold text-white mb-6 text-center">
|
||||
Technology Stack
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{techStack.map((tech, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-slate-800 rounded-lg p-4 border border-slate-600 text-center hover:border-violet-400/50 transition-all duration-300"
|
||||
>
|
||||
<span className="text-gray-300 font-medium">{tech}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
This robust technology stack ensures reliable, secure, and high-performance hosting
|
||||
for all projects while maintaining full control over the infrastructure.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default HostingSecurity;
|
||||
@@ -1,242 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ExternalLink, Github, X } from 'lucide-react';
|
||||
|
||||
interface Project {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
image: string;
|
||||
technologies: string[];
|
||||
liveUrl?: string;
|
||||
githubUrl?: string;
|
||||
category: string;
|
||||
}
|
||||
|
||||
const Portfolio: React.FC = () => {
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
||||
const [filter, setFilter] = useState('all');
|
||||
|
||||
const projects: Project[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: "E-Commerce Platform",
|
||||
description: "Full-stack e-commerce solution with React, Node.js, and PostgreSQL. Features include user authentication, payment processing, inventory management, and admin dashboard.",
|
||||
image: "https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=800&h=600&fit=crop",
|
||||
technologies: ["React", "Node.js", "PostgreSQL", "Stripe"],
|
||||
liveUrl: "https://example.com",
|
||||
githubUrl: "https://github.com",
|
||||
category: "web"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Drone Photography Portfolio",
|
||||
description: "Stunning aerial photography showcase with interactive gallery, location mapping, and client booking system. Built with modern web technologies.",
|
||||
image: "https://images.unsplash.com/photo-1473968512647-3e447244af8f?w=800&h=600&fit=crop",
|
||||
technologies: ["React", "TypeScript", "Mapbox", "Framer Motion"],
|
||||
liveUrl: "https://example.com",
|
||||
githubUrl: "https://github.com",
|
||||
category: "photography"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Trading Dashboard",
|
||||
description: "Real-time trading analytics dashboard with live data visualization, portfolio tracking, and automated trading signals integration.",
|
||||
image: "https://images.unsplash.com/photo-1611974789855-9c2a0a7236a3?w=800&h=600&fit=crop",
|
||||
technologies: ["React", "D3.js", "WebSocket", "Trading212 API"],
|
||||
liveUrl: "https://example.com",
|
||||
githubUrl: "https://github.com",
|
||||
category: "web"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Mobile App Design",
|
||||
description: "Modern mobile application UI/UX design with focus on user experience, accessibility, and brand consistency across iOS and Android platforms.",
|
||||
image: "https://images.unsplash.com/photo-1512941937669-90a1b58e7e9c?w=800&h=600&fit=crop",
|
||||
technologies: ["Figma", "React Native", "TypeScript", "Expo"],
|
||||
liveUrl: "https://example.com",
|
||||
category: "design"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "DevOps Infrastructure",
|
||||
description: "Scalable cloud infrastructure setup with Docker containers, CI/CD pipelines, monitoring, and automated deployment processes.",
|
||||
image: "https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=800&h=600&fit=crop",
|
||||
technologies: ["Docker", "Kubernetes", "AWS", "GitHub Actions"],
|
||||
githubUrl: "https://github.com",
|
||||
category: "devops"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: "AI-Powered Analytics",
|
||||
description: "Machine learning application for data analysis and prediction with interactive visualizations and real-time processing capabilities.",
|
||||
image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&h=600&fit=crop",
|
||||
technologies: ["Python", "TensorFlow", "React", "FastAPI"],
|
||||
liveUrl: "https://example.com",
|
||||
githubUrl: "https://github.com",
|
||||
category: "web"
|
||||
}
|
||||
];
|
||||
|
||||
const categories = [
|
||||
{ id: 'all', name: 'All Projects' },
|
||||
{ id: 'web', name: 'Web Development' },
|
||||
{ id: 'photography', name: 'Photography' },
|
||||
{ id: 'design', name: 'Design' },
|
||||
{ id: 'devops', name: 'DevOps' }
|
||||
];
|
||||
|
||||
const filteredProjects = filter === 'all'
|
||||
? projects
|
||||
: projects.filter(project => project.category === filter);
|
||||
|
||||
return (
|
||||
<section id="portfolio" className="py-20 bg-gradient-to-b from-slate-900 to-slate-800">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl lg:text-5xl font-bold mb-6">
|
||||
<span className="bg-gradient-to-r from-fuchsia-600 to-orange-500 bg-clip-text text-transparent">
|
||||
Portfolio
|
||||
</span>
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-3xl mx-auto">
|
||||
Explore my latest projects showcasing creative solutions in web development,
|
||||
design, and technology innovation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Filter Buttons */}
|
||||
<div className="flex flex-wrap justify-center gap-4 mb-12">
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
key={category.id}
|
||||
onClick={() => setFilter(category.id)}
|
||||
className={`px-6 py-3 rounded-lg font-medium transition-all duration-300 ${
|
||||
filter === category.id
|
||||
? 'bg-gradient-to-r from-fuchsia-600 to-orange-500 text-white shadow-lg'
|
||||
: 'bg-slate-700 text-gray-300 hover:bg-slate-600 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{category.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Projects Grid */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{filteredProjects.map((project) => (
|
||||
<div
|
||||
key={project.id}
|
||||
className="group bg-slate-800 rounded-xl overflow-hidden shadow-lg hover:shadow-2xl hover:shadow-fuchsia-600/10 transition-all duration-300 transform hover:scale-105 cursor-pointer"
|
||||
onClick={() => setSelectedProject(project)}
|
||||
>
|
||||
<div className="relative overflow-hidden">
|
||||
<img
|
||||
src={project.image}
|
||||
alt={project.title}
|
||||
className="w-full h-48 object-cover group-hover:scale-110 transition-transform duration-300"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-semibold text-white mb-2 group-hover:text-fuchsia-600 transition-colors">
|
||||
{project.title}
|
||||
</h3>
|
||||
<p className="text-gray-400 text-sm mb-4 line-clamp-2">
|
||||
{project.description}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.technologies.slice(0, 3).map((tech) => (
|
||||
<span
|
||||
key={tech}
|
||||
className="px-3 py-1 bg-slate-700 text-violet-400 text-xs rounded-full"
|
||||
>
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
{project.technologies.length > 3 && (
|
||||
<span className="px-3 py-1 bg-slate-700 text-gray-400 text-xs rounded-full">
|
||||
+{project.technologies.length - 3} more
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Project Modal */}
|
||||
{selectedProject && (
|
||||
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<div className="bg-slate-800 rounded-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div className="relative">
|
||||
<img
|
||||
src={selectedProject.image}
|
||||
alt={selectedProject.title}
|
||||
className="w-full h-64 lg:h-80 object-cover"
|
||||
/>
|
||||
<button
|
||||
onClick={() => setSelectedProject(null)}
|
||||
className="absolute top-4 right-4 bg-black/50 hover:bg-black/70 text-white p-2 rounded-full transition-all duration-300"
|
||||
aria-label="Close modal"
|
||||
>
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-8">
|
||||
<h3 className="text-3xl font-bold text-white mb-4">{selectedProject.title}</h3>
|
||||
<p className="text-gray-300 text-lg mb-6 leading-relaxed">
|
||||
{selectedProject.description}
|
||||
</p>
|
||||
|
||||
<div className="mb-6">
|
||||
<h4 className="text-lg font-semibold text-white mb-3">Technologies Used:</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedProject.technologies.map((tech) => (
|
||||
<span
|
||||
key={tech}
|
||||
className="px-4 py-2 bg-slate-700 text-violet-400 rounded-lg"
|
||||
>
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
{selectedProject.liveUrl && (
|
||||
<a
|
||||
href={selectedProject.liveUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-fuchsia-600 to-orange-500 text-white font-semibold rounded-lg hover:shadow-lg hover:shadow-fuchsia-600/25 transition-all duration-300"
|
||||
>
|
||||
<ExternalLink size={20} />
|
||||
Live Demo
|
||||
</a>
|
||||
)}
|
||||
{selectedProject.githubUrl && (
|
||||
<a
|
||||
href={selectedProject.githubUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 px-6 py-3 border-2 border-violet-400 text-violet-400 font-semibold rounded-lg hover:bg-violet-400 hover:text-white transition-all duration-300"
|
||||
>
|
||||
<Github size={20} />
|
||||
View Code
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Portfolio;
|
||||
@@ -1,191 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Code, Database, Globe, Server, Wrench, Award } from 'lucide-react';
|
||||
|
||||
interface Skill {
|
||||
name: string;
|
||||
level: number;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
interface SkillCategory {
|
||||
title: string;
|
||||
icon: React.ReactNode;
|
||||
skills: Skill[];
|
||||
color: string;
|
||||
}
|
||||
|
||||
const Skills: React.FC = () => {
|
||||
const skillCategories: SkillCategory[] = [
|
||||
{
|
||||
title: 'Frontend Development',
|
||||
icon: <Globe size={24} />,
|
||||
color: 'from-fuchsia-600 to-pink-600',
|
||||
skills: [
|
||||
{ name: 'React/Next.js', level: 95 },
|
||||
{ name: 'TypeScript', level: 90 },
|
||||
{ name: 'Tailwind CSS', level: 95 },
|
||||
{ name: 'Vue.js', level: 80 },
|
||||
{ name: 'JavaScript ES6+', level: 95 },
|
||||
{ name: 'HTML5/CSS3', level: 98 }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Backend Development',
|
||||
icon: <Server size={24} />,
|
||||
color: 'from-orange-500 to-red-500',
|
||||
skills: [
|
||||
{ name: 'Node.js', level: 90 },
|
||||
{ name: 'Python', level: 85 },
|
||||
{ name: 'PostgreSQL', level: 88 },
|
||||
{ name: 'MongoDB', level: 82 },
|
||||
{ name: 'REST APIs', level: 92 },
|
||||
{ name: 'GraphQL', level: 78 }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'DevOps & Hosting',
|
||||
icon: <Database size={24} />,
|
||||
color: 'from-violet-400 to-purple-600',
|
||||
skills: [
|
||||
{ name: 'Docker', level: 85 },
|
||||
{ name: 'AWS/Cloud', level: 80 },
|
||||
{ name: 'Linux/Ubuntu', level: 88 },
|
||||
{ name: 'Nginx', level: 82 },
|
||||
{ name: 'CI/CD', level: 78 },
|
||||
{ name: 'Self-Hosting', level: 90 }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Other Tools',
|
||||
icon: <Wrench size={24} />,
|
||||
color: 'from-green-500 to-teal-500',
|
||||
skills: [
|
||||
{ name: 'Git/GitHub', level: 95 },
|
||||
{ name: 'Figma/Design', level: 85 },
|
||||
{ name: 'Photoshop', level: 80 },
|
||||
{ name: 'Drone Photography', level: 88 },
|
||||
{ name: 'Video Editing', level: 75 },
|
||||
{ name: 'Trading Analysis', level: 70 }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const experience = [
|
||||
{
|
||||
title: 'Full-Stack Developer',
|
||||
company: 'Freelance',
|
||||
period: '2020 - Present',
|
||||
description: 'Developing custom web applications and providing technical consulting'
|
||||
},
|
||||
{
|
||||
title: 'Frontend Developer',
|
||||
company: 'Tech Startup',
|
||||
period: '2019 - 2020',
|
||||
description: 'Built responsive web applications using React and modern JavaScript'
|
||||
},
|
||||
{
|
||||
title: 'Web Developer',
|
||||
company: 'Digital Agency',
|
||||
period: '2018 - 2019',
|
||||
description: 'Created websites and web applications for various clients'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="skills" className="py-20 bg-gradient-to-b from-slate-900 to-slate-800">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl lg:text-5xl font-bold mb-6">
|
||||
<span className="bg-gradient-to-r from-fuchsia-600 to-orange-500 bg-clip-text text-transparent">
|
||||
Skills & Experience
|
||||
</span>
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-3xl mx-auto">
|
||||
A comprehensive overview of my technical expertise and professional journey
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Experience Section */}
|
||||
<div className="mb-20">
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<Award className="text-fuchsia-600" size={32} />
|
||||
<h3 className="text-3xl font-bold text-white">Experience</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{experience.map((exp, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-slate-800 rounded-xl p-6 border border-slate-700 hover:border-violet-400/50 transition-all duration-300"
|
||||
>
|
||||
<h4 className="text-xl font-semibold text-white mb-2">{exp.title}</h4>
|
||||
<p className="text-fuchsia-600 font-medium mb-2">{exp.company}</p>
|
||||
<p className="text-gray-400 text-sm mb-3">{exp.period}</p>
|
||||
<p className="text-gray-300 text-sm">{exp.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Skills Grid */}
|
||||
<div className="grid lg:grid-cols-2 gap-8">
|
||||
{skillCategories.map((category, categoryIndex) => (
|
||||
<div
|
||||
key={categoryIndex}
|
||||
className="bg-slate-800 rounded-2xl p-8 border border-slate-700 hover:border-violet-400/50 transition-all duration-300"
|
||||
>
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className={`p-3 rounded-xl bg-gradient-to-r ${category.color}`}>
|
||||
<div className="text-white">
|
||||
{category.icon}
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-white">{category.title}</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{category.skills.map((skill, skillIndex) => (
|
||||
<div key={skillIndex}>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-white font-medium">{skill.name}</span>
|
||||
<span className="text-gray-400 text-sm">{skill.level}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-slate-700 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full bg-gradient-to-r ${category.color} transition-all duration-1000 ease-out`}
|
||||
style={{ width: `${skill.level}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Certifications/Achievements */}
|
||||
<div className="mt-16 text-center">
|
||||
<div className="bg-slate-800 rounded-2xl p-8 border border-slate-700">
|
||||
<h3 className="text-2xl font-bold text-white mb-6">Achievements & Certifications</h3>
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="p-4">
|
||||
<div className="text-3xl font-bold text-fuchsia-600 mb-2">50+</div>
|
||||
<p className="text-gray-300">Projects Completed</p>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="text-3xl font-bold text-orange-500 mb-2">5+</div>
|
||||
<p className="text-gray-300">Years Experience</p>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="text-3xl font-bold text-violet-400 mb-2">100%</div>
|
||||
<p className="text-gray-300">Client Satisfaction</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Skills;
|
||||
@@ -1,109 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TrendingUp, DollarSign, BarChart3, Activity } from 'lucide-react';
|
||||
|
||||
const TradingGraph: React.FC = () => {
|
||||
return (
|
||||
<section className="py-20 bg-slate-900">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl lg:text-5xl font-bold mb-6">
|
||||
<span className="bg-gradient-to-r from-fuchsia-600 to-orange-500 bg-clip-text text-transparent">
|
||||
Trading Dashboard
|
||||
</span>
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-3xl mx-auto">
|
||||
Real-time market analysis and portfolio tracking with advanced analytics
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Main Trading Window */}
|
||||
<div className="bg-slate-800 rounded-2xl p-8 shadow-2xl border border-slate-700 hover:border-fuchsia-600/50 transition-all duration-300">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-gradient-to-r from-fuchsia-600 to-orange-500 rounded-lg">
|
||||
<TrendingUp className="text-white" size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-white">Trading212 Portfolio</h3>
|
||||
<p className="text-gray-400">Live market data and analytics</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<span className="text-green-500 font-medium">Live</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Trading Graph Placeholder */}
|
||||
<div className="relative bg-slate-900 rounded-xl p-6 mb-6 min-h-[400px] border border-slate-600">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<BarChart3 className="text-gray-600 mx-auto mb-4" size={64} />
|
||||
<p className="text-gray-400 text-lg">Trading212 Graph Integration</p>
|
||||
<p className="text-gray-500 text-sm mt-2">Real-time market data will be displayed here</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Simulated Graph Elements */}
|
||||
<div className="absolute bottom-6 left-6 right-6">
|
||||
<div className="flex justify-between items-end h-20 opacity-20">
|
||||
{Array.from({ length: 12 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="bg-gradient-to-t from-fuchsia-600 to-orange-500 rounded-t"
|
||||
style={{
|
||||
height: `${Math.random() * 60 + 20}%`,
|
||||
width: '6%'
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="bg-slate-700 rounded-xl p-6 border border-slate-600 hover:border-violet-400/50 transition-all duration-300">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<DollarSign className="text-green-500" size={24} />
|
||||
<h4 className="text-white font-semibold">Portfolio Value</h4>
|
||||
</div>
|
||||
<p className="text-3xl font-bold text-green-500">$12,847.32</p>
|
||||
<p className="text-green-400 text-sm mt-1">+2.34% today</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-700 rounded-xl p-6 border border-slate-600 hover:border-violet-400/50 transition-all duration-300">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Activity className="text-orange-500" size={24} />
|
||||
<h4 className="text-white font-semibold">Active Positions</h4>
|
||||
</div>
|
||||
<p className="text-3xl font-bold text-white">8</p>
|
||||
<p className="text-gray-400 text-sm mt-1">Across 5 markets</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-700 rounded-xl p-6 border border-slate-600 hover:border-violet-400/50 transition-all duration-300">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<TrendingUp className="text-fuchsia-600" size={24} />
|
||||
<h4 className="text-white font-semibold">Monthly Return</h4>
|
||||
</div>
|
||||
<p className="text-3xl font-bold text-fuchsia-600">+8.7%</p>
|
||||
<p className="text-gray-400 text-sm mt-1">Above average</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Integration Note */}
|
||||
<div className="mt-6 p-4 bg-slate-700/50 rounded-lg border border-slate-600">
|
||||
<p className="text-gray-300 text-sm">
|
||||
<span className="text-violet-400 font-medium">Note:</span> This dashboard integrates with Trading212 API
|
||||
for real-time portfolio tracking and market analysis. Data updates every 30 seconds during market hours.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default TradingGraph;
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import Header from '../components/Header';
|
||||
import Hero from '../components/Hero';
|
||||
import Portfolio from '../components/Portfolio';
|
||||
import TradingGraph from '../components/TradingGraph';
|
||||
import DonateShop from '../components/DonateShop';
|
||||
import Skills from '../components/Skills';
|
||||
import HostingSecurity from '../components/HostingSecurity';
|
||||
import Contact from '../components/Contact';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
const Home: React.FC = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-900">
|
||||
<Header />
|
||||
<Hero />
|
||||
<Portfolio />
|
||||
<TradingGraph />
|
||||
<Skills />
|
||||
<DonateShop />
|
||||
<HostingSecurity />
|
||||
<Contact />
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -1,179 +0,0 @@
|
||||
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;
|
||||
@@ -1,5 +0,0 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -1,16 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
"./App.tsx",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
inter: ['Inter', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"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,
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"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,
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
1
frontend_example/vite-env.d.ts
vendored
1
frontend_example/vite-env.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -1,42 +0,0 @@
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
allowedHosts: true,
|
||||
},
|
||||
esbuild: {
|
||||
logOverride: {
|
||||
'ignored-directive': 'silent',
|
||||
},
|
||||
},
|
||||
logLevel: 'info',
|
||||
build: {
|
||||
rollupOptions: {
|
||||
onwarn(warning, warn) {
|
||||
// ignore certain harmless warnings
|
||||
if (
|
||||
warning.message.includes('Module level directives') ||
|
||||
warning.message.includes('"use client"') ||
|
||||
warning.message.includes('"was ignored"')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FAIL build on unresolved imports
|
||||
if (warning.code === 'UNRESOLVED_IMPORT') {
|
||||
throw new Error(`Build failed due to unresolved import:\n${warning.message}`);
|
||||
}
|
||||
|
||||
// FAIL build on missing exports (like your Input error)
|
||||
if (warning.code === 'PLUGIN_WARNING' && /is not exported/.test(warning.message)) {
|
||||
throw new Error(`Build failed due to missing export:\n${warning.message}`);
|
||||
}
|
||||
|
||||
// other warnings: log normally
|
||||
warn(warning);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user