This commit is contained in:
2025-11-06 01:40:00 +01:00
parent de5f54f4bc
commit 602c5a40f1
108 changed files with 9859 additions and 1382 deletions

View File

@@ -0,0 +1,149 @@
import React, { useState } from 'react';
import { Heart, ShoppingCart, Star } from 'lucide-react';
const DonateShop: React.FC = () => {
const [donatedItems, setDonatedItems] = useState<Set<number>>(new Set());
const products = [
{
id: 1,
name: 'Coffee Support',
price: 5,
originalPrice: 10,
image: 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
description: 'Fuel my coding sessions with a virtual coffee',
rating: 4.8,
reviews: 124
},
{
id: 2,
name: 'Meal Contribution',
price: 15,
originalPrice: 25,
image: 'https://images.unsplash.com/photo-1565299624946-b28f40a0ca4b?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
description: 'Help me focus on creating amazing projects',
rating: 4.9,
reviews: 89
},
{
id: 3,
name: 'Project Boost',
price: 25,
originalPrice: 40,
image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
description: 'Support the development of new features',
rating: 5.0,
reviews: 67
},
{
id: 4,
name: 'Monthly Patron',
price: 50,
originalPrice: 75,
image: 'https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
description: 'Ongoing support for continuous improvement',
rating: 4.7,
reviews: 45
}
];
const handleDonate = (productId: number) => {
setDonatedItems(prev => new Set(prev).add(productId));
// Here you would integrate with a payment processor like Stripe
alert(`Thank you for your donation of $${products.find(p => p.id === productId)?.price}!`);
};
return (
<section className="py-16 bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-text sm:text-4xl">
Support My Creative Journey
</h2>
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
Instead of buying products, consider donating to support my creative journey
</p>
</div>
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4">
{products.map((product) => (
<div
key={product.id}
className="bg-background-light rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2 group border border-lines overflow-hidden"
>
<div className="relative overflow-hidden">
<img
className="w-full h-48 object-cover group-hover:scale-110 transition-transform duration-300"
src={product.image}
alt={product.name}
loading="lazy"
/>
<div className="absolute top-4 left-4">
<div className="bg-other text-background px-2 py-1 rounded-full text-xs font-medium">
Donation
</div>
</div>
<div className="absolute top-4 right-4 flex items-center space-x-1">
<Star className="h-4 w-4 text-other fill-current" />
<span className="text-text text-sm font-medium bg-background/70 px-1 rounded">
{product.rating}
</span>
</div>
</div>
<div className="p-6">
<h3 className="text-xl font-semibold text-text mb-2">
{product.name}
</h3>
<p className="text-lines mb-4 text-sm">
{product.description}
</p>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-2">
<span className="text-2xl font-bold text-text">
${product.price}
</span>
<span className="text-sm text-lines line-through">
${product.originalPrice}
</span>
</div>
<span className="text-sm text-lines">
({product.reviews} reviews)
</span>
</div>
<button
onClick={() => handleDonate(product.id)}
disabled={donatedItems.has(product.id)}
className={`w-full flex items-center justify-center px-4 py-3 rounded-lg text-sm font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-other focus:ring-offset-2 ${
donatedItems.has(product.id)
? 'bg-other/20 text-other cursor-not-allowed'
: 'bg-other text-background hover:bg-lines transform hover:scale-105'
}`}
>
{donatedItems.has(product.id) ? (
<>
<Heart className="h-5 w-5 mr-2 fill-current" />
Donated!
</>
) : (
<>
<ShoppingCart className="h-5 w-5 mr-2" />
Donate Now
</>
)}
</button>
</div>
</div>
))}
</div>
<div className="text-center mt-12">
<p className="text-sm text-lines">
All donations go towards improving my portfolio and creating more amazing projects
</p>
</div>
</div>
</section>
);
};
export default DonateShop;

View File

@@ -0,0 +1,99 @@
import React, { useState, useEffect } from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
const DroneVideoCarousel: React.FC = () => {
const [currentIndex, setCurrentIndex] = useState(0);
// Placeholder YouTube video IDs - replace with actual drone video IDs
const videos = [
{ id: 'dQw4w9WgXcQ', title: 'Drone Footage 1' },
{ id: 'dQw4w9WgXcQ', title: 'Drone Footage 2' },
{ id: 'dQw4w9WgXcQ', title: 'Drone Footage 3' },
{ id: 'dQw4w9WgXcQ', title: 'Drone Footage 4' }
];
const nextSlide = () => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % videos.length);
};
const prevSlide = () => {
setCurrentIndex((prevIndex) => (prevIndex - 1 + videos.length) % videos.length);
};
useEffect(() => {
const timer = setInterval(nextSlide, 5000);
return () => clearInterval(timer);
}, []);
return (
<section className="py-20 bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-text sm:text-4xl">
Drone Videography
</h2>
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
Capturing stunning aerial perspectives through professional drone footage
</p>
</div>
<div className="relative max-w-4xl mx-auto">
<div className="aspect-video bg-background-light rounded-xl overflow-hidden shadow-2xl border border-lines">
<AnimatePresence mode="wait">
<motion.div
key={currentIndex}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
className="w-full h-full"
>
<iframe
src={`https://www.youtube.com/embed/${videos[currentIndex].id}?autoplay=0&mute=1&loop=1&playlist=${videos[currentIndex].id}`}
title={videos[currentIndex].title}
className="w-full h-full"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</motion.div>
</AnimatePresence>
</div>
<button
onClick={prevSlide}
className="absolute left-4 top-1/2 transform -translate-y-1/2 bg-boxes hover:bg-other text-text p-3 rounded-full transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2 focus:ring-offset-background"
aria-label="Previous video"
>
<ChevronLeft className="h-6 w-6" />
</button>
<button
onClick={nextSlide}
className="absolute right-4 top-1/2 transform -translate-y-1/2 bg-boxes hover:bg-other text-text p-3 rounded-full transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2 focus:ring-offset-background"
aria-label="Next video"
>
<ChevronRight className="h-6 w-6" />
</button>
<div className="flex justify-center mt-6 space-x-2">
{videos.map((_, index) => (
<button
key={index}
onClick={() => setCurrentIndex(index)}
className={`w-3 h-3 rounded-full transition-all duration-200 ${
index === currentIndex
? 'bg-other'
: 'bg-lines hover:bg-boxes'
}`}
aria-label={`Go to video ${index + 1}`}
/>
))}
</div>
</div>
</div>
</section>
);
};
export default DroneVideoCarousel;

View File

@@ -0,0 +1,127 @@
import React from 'react';
import { Instagram, Twitter, Youtube, Github, Linkedin, Gamepad2, Mail, Phone } from 'lucide-react';
const Footer: React.FC = () => {
const socialLinks = [
{ name: 'Instagram', icon: Instagram, href: '#', color: 'hover:text-other' },
{ name: 'Twitter', icon: Twitter, href: '#', color: 'hover:text-other' },
{ name: 'YouTube', icon: Youtube, href: '#', color: 'hover:text-other' },
{ name: 'GitHub', icon: Github, href: '#', color: 'hover:text-text' },
{ name: 'LinkedIn', icon: Linkedin, href: '#', color: 'hover:text-other' },
{ name: 'Steam', icon: Gamepad2, href: '#', color: 'hover:text-lines' }
];
const footerLinks = [
{
title: 'Portfolio',
links: [
{ name: 'Web Development', href: '/portfolio/web' },
{ name: 'Mobile Apps', href: '/portfolio/mobile' },
{ name: 'UI/UX Design', href: '/portfolio/design' }
]
},
{
title: 'Services',
links: [
{ name: 'Frontend Development', href: '/services/frontend' },
{ name: 'Backend Development', href: '/services/backend' },
{ name: 'Consulting', href: '/services/consulting' }
]
},
{
title: 'Company',
links: [
{ name: 'About', href: '/about' },
{ name: 'Blog', href: '/blog' },
{ name: 'Contact', href: '/contact' }
]
}
];
return (
<footer className="bg-background text-text border-t border-lines">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{/* Brand Section */}
<div className="lg:col-span-1">
<div className="flex items-center">
<span className="text-2xl font-bold text-other">
Portfolio
</span>
</div>
<p className="mt-4 text-lines text-sm">
Creating exceptional digital experiences through innovative web development and beautiful design.
</p>
<div className="mt-6 flex space-x-4">
{socialLinks.map((social) => {
const IconComponent = social.icon;
return (
<a
key={social.name}
href={social.href}
className={`text-lines ${social.color} transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-other focus:ring-offset-2 focus:ring-offset-background rounded`}
aria-label={social.name}
>
<IconComponent className="h-5 w-5" />
</a>
);
})}
</div>
<div className="mt-6 space-y-2">
<div className="flex items-center text-lines text-sm">
<Mail className="h-4 w-4 mr-2" />
hello@example.com
</div>
<div className="flex items-center text-lines text-sm">
<Phone className="h-4 w-4 mr-2" />
+1 (555) 123-4567
</div>
</div>
</div>
{/* Links Sections */}
{footerLinks.map((section) => (
<div key={section.title}>
<h3 className="text-sm font-semibold text-text uppercase tracking-wider">
{section.title}
</h3>
<ul className="mt-4 space-y-2">
{section.links.map((link) => (
<li key={link.name}>
<a
href={link.href}
className="text-lines hover:text-other transition-colors duration-200 text-sm focus:outline-none focus:ring-2 focus:ring-other focus:ring-offset-2 focus:ring-offset-background rounded"
>
{link.name}
</a>
</li>
))}
</ul>
</div>
))}
</div>
<div className="mt-8 pt-8 border-t border-boxes">
<div className="flex flex-col md:flex-row justify-between items-center">
<p className="text-lines text-sm">
© 2025 Portfolio. All rights reserved.
</p>
<div className="mt-4 md:mt-0 flex space-x-6">
<a href="/privacy" className="text-lines hover:text-text text-sm transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-other focus:ring-offset-2 focus:ring-offset-background rounded">
Privacy Policy
</a>
<a href="/terms" className="text-lines hover:text-text text-sm transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-other focus:ring-offset-2 focus:ring-offset-background rounded">
Terms of Service
</a>
</div>
<p className="text-lines text-sm mt-4 md:mt-0">
Built with by <a rel="nofollow" target="_blank" href="https://meku.dev" className="text-other hover:text-lines transition-colors duration-200">Meku.dev</a>
</p>
</div>
</div>
</div>
</footer>
);
};
export default Footer;

View File

@@ -1,40 +0,0 @@
footer a{
color: var(--c-text);
text-decoration: none;
}
footer a i{
color: white;
text-decoration: none;
}
footer a:hover i{
color: var(--c-text);
text-decoration: none;
}
footer{
font-family: "Roboto Mono", monospace;
background-color: var(--c-boxes);
margin-top: 2em;
display: flex;
color: white;
align-items: center;
justify-content: space-evenly;
}
footer address{
padding: 1em;
font-style: normal;
}
footer .contacts{
font-size: 2em;
}
@media only screen and (max-width: 990px){
footer{
flex-direction: column;
padding-bottom: 1em;
padding-top: 1em;
}
}

View File

@@ -1,76 +0,0 @@
import styles from "./footer.module.css"
export default function Footer() {
return (
<footer id="contacts">
<div>
<h1>vontor.cz</h1>
</div>
<address>
Written by <b>David Bruno Vontor | © 2025</b>
<br />
<p>
Tel.:{" "}
<a href="tel:+420605512624">
<u>+420 605 512 624</u>
</a>
</p>
<p>
E-mail:{" "}
<a href="mailto:brunovontor@gmail.com">
<u>brunovontor@gmail.com</u>
</a>
</p>
<p>
IČO:{" "}
<a
href="https://www.rzp.cz/verejne-udaje/cs/udaje/vyber-subjektu;ico=21613109;"
target="_blank"
rel="noopener noreferrer"
>
<u>21613109</u>
</a>
</p>
</address>
<div className="contacts">
<a
href="https://github.com/Brunobrno"
target="_blank"
rel="noopener noreferrer"
>
<i className="fa fa-github"></i>
</a>
<a
href="https://www.instagram.com/brunovontor/"
target="_blank"
rel="noopener noreferrer"
>
<i className="fa fa-instagram"></i>
</a>
<a
href="https://twitter.com/BVontor"
target="_blank"
rel="noopener noreferrer"
>
<i className="fa-brands fa-x-twitter"></i>
</a>
<a
href="https://steamcommunity.com/id/Brunobrno/"
target="_blank"
rel="noopener noreferrer"
>
<i className="fa-brands fa-steam"></i>
</a>
<a
href="https://www.youtube.com/@brunovontor"
target="_blank"
rel="noopener noreferrer"
>
<i className="fa-brands fa-youtube"></i>
</a>
</div>
</footer>
)
}

View File

@@ -1,85 +0,0 @@
import React, { useState, useRef } from "react"
import styles from "./contact-me.module.css"
import { LuMousePointerClick } from "react-icons/lu";
export default function ContactMeForm() {
const [opened, setOpened] = useState(false)
const [contentMoveUp, setContentMoveUp] = useState(false)
const [openingBehind, setOpeningBehind] = useState(false)
const [success, setSuccess] = useState(false)
const openingRef = useRef<HTMLDivElement>(null)
function handleSubmit() {
// form submission logic here
}
const toggleOpen = () => {
if (!opened) {
setOpened(true)
setOpeningBehind(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
}
}
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
if (opened && e.propertyName === "transform") {
setContentMoveUp(true)
// Move the opening behind after the animation
setTimeout(() => setOpeningBehind(true), 10)
}
if (!opened && e.propertyName === "transform") {
setOpeningBehind(false)
}
}
return (
<div className={styles["contact-me"]}>
<div
ref={openingRef}
className={
[
styles.opening,
opened ? styles["rotate-opening"] : "",
openingBehind ? styles["opening-behind"] : ""
].filter(Boolean).join(" ")
}
onClick={toggleOpen}
onTransitionEnd={handleTransitionEnd}
>
<LuMousePointerClick/>
</div>
<div
className={
contentMoveUp
? `${styles.content} ${styles["content-moveup"]}`
: styles.content
}
>
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
placeholder="Váš email"
required
/>
<textarea
name="message"
placeholder="Vaše zpráva"
required
/>
<input type="submit"/>
</form>
</div>
<div className={styles.cover}></div>
<div className={styles.triangle}></div>
</div>
)
}

View File

@@ -1,145 +0,0 @@
.contact-me {
margin: 5em auto;
position: relative;
aspect-ratio: 16 / 9;
background-color: #c8c8c8;
max-width: 100vw;
}
.contact-me + .mail-box{
}
.contact-me .opening {
width: 100%;
height: 100%;
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;
text-align: center;
}
.rotate-opening{
background-color: #c8c8c8;
transform: rotateX(180deg);
}
.opening svg{
margin: auto;
font-size: 3em;
margin-top: -0.5em;
}
.contact-me .content {
position: relative;
height: 100%;
width: 100%;
z-index: 1;
transition: all 1s ease-out;
}
.content-moveup{
transform: translateY(-70%);
}
.content-moveup-index {
z-index: 2 !important;
}
.contact-me .content form{
width: 80%;
display: flex;
gap: 1em;
flex-direction: column;
align-items: flex-start;
justify-content: center;
margin: auto;
background-color: #deefff;
padding: 1em;
border: 0.5em dashed #88d4ed;
border-radius: 0.25em;
}
.contact-me .content form div{
width: -webkit-fill-available;
display: flex;
flex-direction: column;
}
.contact-me .content form input[type=submit]{
margin: auto;
border: none;
background: #4ca4d5;
color: #ffffff;
padding: 1em 1.5em;
cursor: pointer;
border-radius: 1em;
}
.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;
}
.opening-behind { z-index: 0 !important; }
.contact-me .cover {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
clip-path: polygon(0 0, 50% 50%, 100% 0, 100% 100%, 0 100%);
background-color: #f0f0f0;
}
.contact-me .triangle{
position: absolute;
bottom: 0;
right: 0;
z-index: 3;
width: 100%;
height: 100%;
clip-path: polygon(100% 0, 0 100%, 100% 100%);
background-color: rgb(255 255 255);
}
@keyframes shake {
0% { transform: translateX(0); }
25% { transform: translateX(-2px) rotate(-8deg); }
50% { transform: translateX(2px) rotate(4deg); }
75% { transform: translateX(-1px) rotate(-2deg); }
100% { transform: translateX(0); }
}
.contact-me .opening i {
color: #797979;
font-size: 5em;
display: inline-block;
animation: 0.4s ease-in-out 2s infinite normal none running shake;
animation-delay: 2s;
animation-iteration-count: infinite;
}
@media only screen and (max-width: 990px){
.contact-me{
aspect-ratio: unset;
margin-top: 7ch;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -0,0 +1,186 @@
import React, { useState, useRef, useEffect } from 'react';
import { ChevronDown, Menu, X } from 'lucide-react';
const Header: React.FC = () => {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const handleMouseEnter = (menu: string) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setActiveDropdown(menu);
};
const handleMouseLeave = () => {
timeoutRef.current = setTimeout(() => {
setActiveDropdown(null);
}, 150);
};
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
const navigationItems = [
{ name: 'Home', href: '/' },
{
name: 'Portfolio',
href: '/portfolio',
submenu: [
{ name: 'Web Development', href: '/portfolio/web' },
{ name: 'Mobile Apps', href: '/portfolio/mobile' },
{ name: 'UI/UX Design', href: '/portfolio/design' },
{ name: 'E-commerce', href: '/portfolio/ecommerce' }
]
},
{
name: 'Services',
href: '/services',
submenu: [
{ name: 'Frontend Development', href: '/services/frontend' },
{ name: 'Backend Development', href: '/services/backend' },
{ name: 'Full Stack Solutions', href: '/services/fullstack' },
{ name: 'Consulting', href: '/services/consulting' }
]
},
{
name: 'About',
href: '/about',
submenu: [
{ name: 'My Story', href: '/about/story' },
{ name: 'Skills', href: '/about/skills' },
{ name: 'Experience', href: '/about/experience' },
{ name: 'Testimonials', href: '/about/testimonials' }
]
},
{ name: 'Blog', href: '/blog' },
{ name: 'Contact', href: '/contact' }
];
return (
<header className="bg-background shadow-lg sticky top-0 z-50">
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" role="navigation" aria-label="Main navigation">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<div className="flex-shrink-0">
<a href="/" className="text-2xl font-bold text-other hover:text-lines transition-colors">
Portfolio
</a>
</div>
{/* Desktop Navigation */}
<div className="hidden md:block">
<div className="ml-10 flex items-baseline space-x-4">
{navigationItems.map((item) => (
<div
key={item.name}
className="relative"
onMouseEnter={() => item.submenu && handleMouseEnter(item.name)}
onMouseLeave={handleMouseLeave}
>
<a
href={item.href}
className="flex items-center px-3 py-2 rounded-md text-sm font-medium text-text hover:text-other hover:bg-background-light transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
aria-haspopup={item.submenu ? 'true' : 'false'}
aria-expanded={activeDropdown === item.name ? 'true' : 'false'}
>
{item.name}
{item.submenu && (
<ChevronDown
className={`ml-1 h-4 w-4 transition-transform duration-200 ${
activeDropdown === item.name ? 'rotate-180' : ''
}`}
aria-hidden="true"
/>
)}
</a>
{/* Dropdown Menu */}
{item.submenu && (
<div
className={`absolute left-0 mt-2 w-56 rounded-md shadow-lg bg-background-light ring-1 ring-lines ring-opacity-30 transition-all duration-200 ${
activeDropdown === item.name
? 'opacity-100 visible transform translate-y-0'
: 'opacity-0 invisible transform -translate-y-2'
}`}
role="menu"
aria-orientation="vertical"
>
<div className="py-1">
{item.submenu.map((subItem) => (
<a
key={subItem.name}
href={subItem.href}
className="block px-4 py-2 text-sm text-text hover:bg-boxes hover:text-other transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-inset"
role="menuitem"
>
{subItem.name}
</a>
))}
</div>
</div>
)}
</div>
))}
</div>
</div>
{/* Mobile menu button */}
<div className="md:hidden">
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="inline-flex items-center justify-center p-2 rounded-md text-text hover:text-other hover:bg-background-light focus:outline-none focus:ring-2 focus:ring-inset focus:ring-lines transition-colors duration-200"
aria-expanded={isMobileMenuOpen}
aria-label="Toggle mobile menu"
>
{isMobileMenuOpen ? (
<X className="block h-6 w-6" aria-hidden="true" />
) : (
<Menu className="block h-6 w-6" aria-hidden="true" />
)}
</button>
</div>
</div>
{/* Mobile Navigation */}
<div className={`md:hidden transition-all duration-300 ease-in-out ${
isMobileMenuOpen ? 'max-h-screen opacity-100' : 'max-h-0 opacity-0 overflow-hidden'
}`}>
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3 bg-background-light rounded-lg mt-2">
{navigationItems.map((item) => (
<div key={item.name}>
<a
href={item.href}
className="block px-3 py-2 rounded-md text-base font-medium text-text hover:text-other hover:bg-boxes transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
>
{item.name}
</a>
{item.submenu && (
<div className="ml-4 space-y-1">
{item.submenu.map((subItem) => (
<a
key={subItem.name}
href={subItem.href}
className="block px-3 py-2 rounded-md text-sm text-lines hover:text-other hover:bg-boxes transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
>
{subItem.name}
</a>
))}
</div>
)}
</div>
))}
</div>
</div>
</nav>
</header>
);
};
export default Header;

View File

@@ -0,0 +1,59 @@
import React from 'react';
import { ArrowRight, Download } from 'lucide-react';
const Hero: React.FC = () => {
return (
<section className="relative bg-background-light py-20 lg:py-32 overflow-hidden">
<div className="absolute inset-0 bg-grid-pattern opacity-5"></div>
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="lg:grid lg:grid-cols-12 lg:gap-8 items-center">
<div className="sm:text-center md:max-w-2xl md:mx-auto lg:col-span-6 lg:text-left">
<h1 className="text-4xl font-bold text-text sm:text-5xl lg:text-6xl">
<span className="block">Creative</span>
<span className="block text-other">
Developer
</span>
</h1>
<p className="mt-6 text-lg text-lines sm:text-xl max-w-3xl">
I craft exceptional digital experiences through innovative web development,
combining cutting-edge technology with beautiful design to bring your ideas to life.
</p>
<div className="mt-8 sm:max-w-lg sm:mx-auto sm:text-center lg:text-left lg:mx-0">
<div className="flex flex-col sm:flex-row gap-4">
<a
href="/portfolio"
className="inline-flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-lg text-background bg-other hover:bg-lines focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-other transition-all duration-200 transform hover:scale-105"
>
View My Work
<ArrowRight className="ml-2 h-5 w-5" aria-hidden="true" />
</a>
<a
href="/resume.pdf"
className="inline-flex items-center justify-center px-6 py-3 border-2 border-lines text-base font-medium rounded-lg text-text bg-transparent hover:bg-background hover:border-other hover:text-other focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-other transition-all duration-200"
>
<Download className="mr-2 h-5 w-5" aria-hidden="true" />
Download Resume
</a>
</div>
</div>
</div>
<div className="mt-12 relative sm:max-w-lg sm:mx-auto lg:mt-0 lg:max-w-none lg:mx-0 lg:col-span-6 lg:flex lg:items-center">
<div className="relative mx-auto w-full rounded-lg shadow-lg lg:max-w-md">
<div className="relative block w-full bg-boxes rounded-lg overflow-hidden">
<img
className="w-full h-96 object-cover"
src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80"
alt="Professional developer portrait"
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-tr from-boxes/40 to-other/20"></div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default Hero;

View File

@@ -0,0 +1,110 @@
import React from 'react';
import { ExternalLink, Github } from 'lucide-react';
const Portfolio: React.FC = () => {
const projects = [
{
title: 'E-Commerce Platform',
description: 'A full-stack e-commerce solution with React, Node.js, and Stripe integration.',
image: 'https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
tags: ['React', 'Node.js', 'MongoDB', 'Stripe'],
liveUrl: '#',
githubUrl: '#'
},
{
title: 'Task Management App',
description: 'A collaborative project management tool with real-time updates and team features.',
image: 'https://images.unsplash.com/photo-1611224923853-80b023f02d71?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
tags: ['Vue.js', 'Firebase', 'Tailwind CSS'],
liveUrl: '#',
githubUrl: '#'
},
{
title: 'Weather Dashboard',
description: 'A responsive weather application with location-based forecasts and data visualization.',
image: 'https://images.unsplash.com/photo-1504608524841-42fe6f032b4b?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
tags: ['React', 'TypeScript', 'Chart.js', 'API'],
liveUrl: '#',
githubUrl: '#'
}
];
return (
<section className="py-20 bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h2 className="text-3xl font-bold text-text sm:text-4xl">
Featured Projects
</h2>
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
A showcase of my recent work and creative solutions
</p>
</div>
<div className="mt-16 grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
{projects.map((project, index) => (
<div
key={project.title}
className="bg-background-light rounded-xl shadow-lg overflow-hidden hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2 group border border-lines"
>
<div className="relative overflow-hidden">
<img
className="w-full h-48 object-cover group-hover:scale-110 transition-transform duration-300"
src={project.image}
alt={project.title}
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-background/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
<div className="absolute top-4 right-4 flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<a
href={project.liveUrl}
className="p-2 bg-boxes rounded-full hover:bg-other transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
aria-label={`View ${project.title} live`}
>
<ExternalLink className="h-4 w-4 text-text" />
</a>
<a
href={project.githubUrl}
className="p-2 bg-boxes rounded-full hover:bg-other transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
aria-label={`View ${project.title} source code`}
>
<Github className="h-4 w-4 text-text" />
</a>
</div>
</div>
<div className="p-6">
<h3 className="text-xl font-semibold text-text mb-2">
{project.title}
</h3>
<p className="text-lines mb-4">
{project.description}
</p>
<div className="flex flex-wrap gap-2">
{project.tags.map((tag) => (
<span
key={tag}
className="px-3 py-1 text-xs font-medium bg-boxes text-other rounded-full"
>
{tag}
</span>
))}
</div>
</div>
</div>
))}
</div>
<div className="text-center mt-12">
<a
href="/portfolio"
className="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-lg text-background bg-other hover:bg-lines focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-other transition-all duration-200 transform hover:scale-105"
>
View All Projects
</a>
</div>
</div>
</section>
);
};
export default Portfolio;

View File

@@ -0,0 +1,115 @@
import React from 'react';
import { Briefcase, Code, Database, Palette, Smartphone, Zap } from 'lucide-react';
const Skills: React.FC = () => {
const experience = [
{
title: 'Senior Full-Stack Developer',
company: 'Tech Innovations Inc.',
period: '2022 - Present',
description: 'Leading development of scalable web applications using React, Node.js, and cloud technologies.'
},
{
title: 'Frontend Developer',
company: 'Digital Solutions Ltd.',
period: '2020 - 2022',
description: 'Built responsive user interfaces and improved performance for e-commerce platforms.'
},
{
title: 'Junior Developer',
company: 'StartupXYZ',
period: '2019 - 2020',
description: 'Developed mobile applications and contributed to backend API development.'
}
];
const specificSkills = [
{ name: 'React', level: 95, icon: Code },
{ name: 'TypeScript', level: 90, icon: Code },
{ name: 'Node.js', level: 85, icon: Database },
{ name: 'Python', level: 80, icon: Code },
{ name: 'UI/UX Design', level: 75, icon: Palette },
{ name: 'Mobile Development', level: 70, icon: Smartphone }
];
return (
<section className="py-20 bg-background-light">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h2 className="text-3xl font-bold text-text sm:text-4xl">
Experience & Skills
</h2>
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
A comprehensive overview of my professional journey and technical expertise
</p>
</div>
<div className="mt-16 grid grid-cols-1 lg:grid-cols-2 gap-12">
{/* Experience Section */}
<div>
<div className="flex items-center mb-8">
<div className="p-3 bg-other rounded-lg mr-4">
<Briefcase className="h-6 w-6 text-background" />
</div>
<h3 className="text-2xl font-bold text-text">Experience</h3>
</div>
<div className="space-y-6">
{experience.map((exp, index) => (
<div
key={index}
className="bg-background rounded-lg p-6 border border-lines"
>
<h4 className="text-lg font-semibold text-text mb-1">
{exp.title}
</h4>
<p className="text-other font-medium mb-2">
{exp.company} {exp.period}
</p>
<p className="text-lines text-sm">
{exp.description}
</p>
</div>
))}
</div>
</div>
{/* Specific Skills Section */}
<div>
<div className="flex items-center mb-8">
<div className="p-3 bg-boxes rounded-lg mr-4">
<Zap className="h-6 w-6 text-text" />
</div>
<h3 className="text-2xl font-bold text-text">Specific Skills</h3>
</div>
<div className="space-y-6">
{specificSkills.map((skill, index) => {
const IconComponent = skill.icon;
return (
<div key={skill.name} className="bg-background rounded-lg p-6 shadow-md border border-lines">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center">
<div className="p-2 bg-boxes rounded-lg mr-3">
<IconComponent className="h-5 w-5 text-other" />
</div>
<span className="font-semibold text-text">{skill.name}</span>
</div>
<span className="text-sm font-medium text-lines">{skill.level}%</span>
</div>
<div className="w-full bg-background-light rounded-full h-2">
<div
className="bg-other h-2 rounded-full transition-all duration-1000 ease-out"
style={{ width: `${skill.level}%` }}
></div>
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
</section>
);
};
export default Skills;

View File

@@ -0,0 +1,100 @@
import React from 'react';
import { TrendingUp, BarChart3 } from 'lucide-react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
// Placeholder data - replace with actual Trading212 API data
const data = [
{ date: '2024-01', value: 1000 },
{ date: '2024-02', value: 1200 },
{ date: '2024-03', value: 1100 },
{ date: '2024-04', value: 1400 },
{ date: '2024-05', value: 1300 },
{ date: '2024-06', value: 1600 }
];
const TradingGraph: React.FC = () => {
return (
<section className="py-16 bg-background-light">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-text sm:text-4xl">
Trading Performance
</h2>
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
Real-time insights from Trading212 portfolio tracking
</p>
</div>
<div className="bg-background rounded-xl shadow-lg p-6 border border-lines">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-3">
<div className="p-2 bg-other rounded-lg">
<BarChart3 className="h-6 w-6 text-background" />
</div>
<div>
<h3 className="text-lg font-semibold text-text">Portfolio Value</h3>
<p className="text-sm text-lines">Trading212 Integration</p>
</div>
</div>
<div className="text-right">
<div className="text-2xl font-bold text-text">$1,600</div>
<div className="flex items-center text-sm text-other">
<TrendingUp className="h-4 w-4 mr-1" />
+12.5% this month
</div>
</div>
</div>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--c-lines)" />
<XAxis
dataKey="date"
stroke="var(--c-lines)"
fontSize={12}
tickLine={false}
axisLine={false}
/>
<YAxis
stroke="var(--c-lines)"
fontSize={12}
tickLine={false}
axisLine={false}
tickFormatter={(value) => `$${value}`}
/>
<Tooltip
contentStyle={{
backgroundColor: 'var(--c-background-light)',
border: '1px solid var(--c-lines)',
borderRadius: '8px',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
color: 'var(--c-text)'
}}
labelStyle={{ color: 'var(--c-text)' }}
formatter={(value: number) => [`$${value}`, 'Value']}
/>
<Line
type="monotone"
dataKey="value"
stroke="var(--c-other)"
strokeWidth={3}
dot={{ fill: '#c026d3', strokeWidth: 2, r: 4 }}
activeDot={{ r: 6, stroke: '#c026d3', strokeWidth: 2, fill: '#ffffff' }}
/>
</LineChart>
</ResponsiveContainer>
</div>
<div className="mt-6 text-center">
<p className="text-sm text-slate-500">
API integration pending - displaying sample data
</p>
</div>
</div>
</div>
</section>
);
};
export default TradingGraph;

View File

@@ -0,0 +1,83 @@
import React from 'react';
import { ExternalLink } from 'lucide-react';
const WebsiteScreenshots: React.FC = () => {
const websites = [
{
title: 'E-Commerce Platform',
image: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
url: '#',
description: 'Modern online store with seamless checkout'
},
{
title: 'Portfolio Website',
image: 'https://images.unsplash.com/photo-1467232004584-a241de8bcf5d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
url: '#',
description: 'Creative showcase for digital artists'
},
{
title: 'Business Landing Page',
image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
url: '#',
description: 'Professional B2B service presentation'
},
{
title: 'Blog Platform',
image: 'https://images.unsplash.com/photo-1486312338219-ce68e2c6f44d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
url: '#',
description: 'Content management system for writers'
}
];
return (
<section className="py-16 bg-background-light">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-text sm:text-4xl">
Website Screenshots
</h2>
<p className="mt-4 text-lg text-lines max-w-2xl mx-auto">
A glimpse of recent web development projects and designs
</p>
</div>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
{websites.map((website, index) => (
<div
key={website.title}
className="group bg-background rounded-lg shadow-md hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1 border border-lines overflow-hidden"
>
<div className="relative overflow-hidden">
<img
className="w-full h-32 object-cover group-hover:scale-105 transition-transform duration-300"
src={website.image}
alt={`${website.title} screenshot`}
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-background/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end justify-end p-2">
<a
href={website.url}
className="p-2 bg-boxes rounded-full hover:bg-other transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-lines focus:ring-offset-2"
aria-label={`View ${website.title}`}
>
<ExternalLink className="h-4 w-4 text-text" />
</a>
</div>
</div>
<div className="p-4">
<h3 className="text-sm font-semibold text-text mb-1">
{website.title}
</h3>
<p className="text-xs text-lines">
{website.description}
</p>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default WebsiteScreenshots;

View File

@@ -1,90 +0,0 @@
import React, { useEffect, useRef } from "react"
import styles from "./drone.module.css"
export default function Drone() {
const videoRef = useRef<HTMLVideoElement | null>(null)
const sourceRef = useRef<HTMLSourceElement | null>(null)
useEffect(() => {
function setVideoDroneQuality() {
if (!sourceRef.current || !videoRef.current) return
const videoSources = {
fullHD: "static/home/video/drone-background-video-1080p.mp4", // For desktops (1920x1080)
hd: "static/home/video/drone-background-video-720p.mp4", // For tablets/smaller screens (1280x720)
lowRes: "static/home/video/drone-background-video-480p.mp4" // For mobile devices or low performance (854x480)
}
const screenWidth = window.innerWidth
// Pick appropriate source
if (screenWidth >= 1920) {
sourceRef.current.src =
"https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.fullHD
} else if (screenWidth >= 1280) {
sourceRef.current.src =
"https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.hd
} else {
sourceRef.current.src =
"https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.lowRes
}
// Reload video
videoRef.current.load()
console.log("Drone video set!")
}
// Run once on mount
setVideoDroneQuality()
// Optional: rerun on resize
window.addEventListener("resize", setVideoDroneQuality)
return () => {
window.removeEventListener("resize", setVideoDroneQuality)
}
}, [])
return (
<div className={`${styles.drone}`}>
<video
ref={videoRef}
id="drone-video"
className={styles.videoBackground}
autoPlay
muted
loop
playsInline
>
<source ref={sourceRef} id="video-source" type="video/mp4" />
Your browser does not support video.
</video>
<article>
<header>
<h1>Letecké záběry, co zaujmou</h1>
</header>
<main>
<section>
<h2>Oprávnění</h2>
<p>Oprávnění A1/A2/A3 + radiostanice. Bezpečný provoz i v omezených zónách, povolení zajistím.</p>
</section>
<section>
<h2>Cena</h2>
<p>Paušál 3000. Ostrava zdarma; mimo 10/km. Cena se může lišit dle povolení.</p>
</section>
<section>
<h2>Výstup</h2>
<p>Krátký sestřih nebo surové záběry podle potřeby.</p>
</section>
</main>
<div>
<a href="#contacts">Zájem?</a>
</div>
</article>
</div>
)
}

View File

@@ -1,103 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
.drone{
margin-top: -4em;
font-style: normal;
width: 100%;
position: relative;
color: white;
}
.drone .videoBackground {
height: 100%;
width: 100%;
position: absolute;
object-fit: cover;
z-index: -1;
clip-path: polygon(0 3%, 15% 0, 30% 7%, 42% 3%, 61% 1%, 82% 5%, 100% 1%, 100% 94%, 82% 100%, 65% 96%, 47% 99%, 30% 90%, 14% 98%, 0 94%);
}
.drone article{
padding: 5em;
display: flex;
border-radius: 2em;
padding: 3em;
gap: 2em;
position: relative;
align-items: center;
flex-direction: column;
justify-content: space-evenly;
}
.drone article header h1{
font-size: 4em;
font-weight: 300;
}
.drone article header{
flex: 1;
}
.drone article main{
width: 90%;
display: flex;
font-size: 1em;
/* width: 60%; */
flex: 2;
flex-direction: row;
font-weight: 400;
gap: 2em;
/* flex-wrap: wrap; */
justify-content: space-evenly;
}
.drone a{
color: white;
}
.drone article div{
display: flex;
flex: 1;
font-size: 1.25em;
margin-top: 1em;
margin-bottom: 1em;
flex-direction: column;
align-items: center;
font-weight: 400;
}
@media only screen and (max-width: 990px) {
.drone article header h1{
font-size: 2.3em;
font-weight: 200;
}
.drone article header{
text-align: center;
}
.drone article main{
flex-direction: column;
font-size: 1em;
}
.drone article{
height: auto;
}
.drone article div{
margin: 2em;
text-align: center;
}
.drone video{
display: none;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -1,168 +0,0 @@
.portfolio {
margin: auto;
margin-top: 10em;
width: 80%;
display: flex;
flex-wrap: wrap;
align-content: center;
color: white;
position: relative;
}
.portfolio div .door {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
bottom: 0;
left: 0;
height: 100%;
width: 100%;
background-color: #c2a67d;
color: #5e5747;
border-radius: 1em;
transform-origin: bottom;
transition: transform 0.5s ease-in-out;
transform: skew(-5deg);
z-index: 3;
box-shadow: #000000 5px 5px 15px;
}
.portfolio div span svg{
font-size: 5em;
cursor: pointer;
animation: shake 0.4s ease-in-out infinite;
}
@keyframes shake {
0% { transform: translateX(0); }
25% { transform: translateX(-2px) rotate(-8deg); }
50% { transform: translateX(2px) rotate(4deg); }
75% { transform: translateX(-1px) rotate(-2deg); }
100% { transform: translateX(0); }
}
.door i{
color: #5e5747;
font-size: 5em;
display: inline-block;
animation: shake 0.4s ease-in-out infinite;
animation-delay: 2s;
animation-iteration-count: infinite;
}
.portfolio .door-open{
transform: rotateX(90deg) skew(-2deg) !important;
}
.portfolio>header {
width: fit-content;
position: absolute;
z-index: 0;
top: -3.7em;
left: 0;
padding: 1em 3em;
padding-bottom: 0;
background-color: #cdc19c;
color: #5e5747;
border-top-left-radius: 1em;
border-top-right-radius: 1em;
}
.portfolio>header h1 {
font-size: 2.5em;
padding: 0;
}
.portfolio>header i {
font-size: 6em;
}
.portfolio article{
position: relative;
}
.portfolio article::after{
clip-path: polygon(0% 0%, 11% 12.5%, 0% 25%, 11% 37.5%, 0% 50%, 11% 62.5%, 0% 75%, 11% 87.5%, 0% 100%, 100% 100%, 84% 87.5%, 98% 75%, 86% 62.5%, 100% 50%, 86% 37.5%, 100% 25%, 93% 12.5%, 100% 0%);
content: "";
bottom: 0;
right: -2em;
height: 2em;
width: 6em;
transform: rotate(-45deg);
position: absolute;
background-color: rgba(255, 255, 255, 0.5);
}
.portfolio article::before{
clip-path: polygon(0% 0%, 11% 12.5%, 0% 25%, 11% 37.5%, 0% 50%, 11% 62.5%, 0% 75%, 11% 87.5%, 0% 100%, 100% 100%, 84% 87.5%, 98% 75%, 86% 62.5%, 100% 50%, 86% 37.5%, 100% 25%, 93% 12.5%, 100% 0%);
content: "";
top: 0;
left: -2em;
height: 2em;
width: 6em;
transform: rotate(-45deg);
position: absolute;
background-color: rgba(255, 255, 255, 0.5);
}
.portfolio article header {
display: flex;
flex-direction: column;
align-items: center;
}
.portfolio div {
width: 100%;
padding: 3em;
background-color: #cdc19c;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 5em;
border-radius: 1em;
border-top-left-radius: 0;
aspect-ratio: 16 / 9;
}
.portfolio div article {
display: flex;
border-radius: 0em;
background-color: #9c885c;
width: 30%;
text-align: center;
flex-direction: column;
justify-content: center;
align-items: center;
}
.portfolio div article header a img {
width: 80%;
margin: auto;
}
@media only screen and (max-width: 990px) {
.portfolio div{
flex-direction: column;
align-items: center;
}
.portfolio div article{
width: 100%;
}
}

View File

@@ -1,86 +0,0 @@
import React, { useState } from "react"
import styles from "./Portfolio.module.css"
import { LuMousePointerClick } from "react-icons/lu";
interface PortfolioItem {
href: string
src: string
alt: string
// Optional per-item styling (prefer Tailwind utility classes in className/imgClassName)
className?: string
imgClassName?: string
style?: React.CSSProperties
imgStyle?: React.CSSProperties
}
const portfolioItems: PortfolioItem[] = [
{
href: "https://davo1.cz",
src: "/portfolio/davo1.png",
alt: "davo1.cz logo",
imgClassName: "bg-black rounded-lg p-4",
//className: "bg-white/5 rounded-lg p-4",
},
{
href: "https://perlica.cz",
src: "/portfolio/perlica.png",
alt: "Perlica logo",
imgClassName: "rounded-lg",
// imgClassName: "max-h-12",
},
{
href: "http://epinger2.cz",
src: "/portfolio/epinger.png",
alt: "Epinger2 logo",
imgClassName: "bg-white rounded-lg",
// imgClassName: "max-h-12",
},
]
export default function Portfolio() {
const [doorOpen, setDoorOpen] = useState(false)
const toggleDoor = () => setDoorOpen((prev) => !prev)
return (
<div className={styles.portfolio} id="portfolio">
<header>
<h1>Portfolio</h1>
</header>
<div>
<span
className={
doorOpen
? `${styles.door} ${styles["door-open"]}`
: styles.door
}
onClick={toggleDoor}
>
<LuMousePointerClick/>
</span>
{portfolioItems.map((item, index) => (
<article
key={index}
className={`${styles.article} ${item.className ?? ""}`}
style={item.style}
>
<header>
<a href={item.href} target="_blank" rel="noopener noreferrer">
<img
src={item.src}
alt={item.alt}
className={item.imgClassName}
style={item.imgStyle}
/>
</a>
</header>
<main></main>
</article>
))}
</div>
</div>
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -1,304 +0,0 @@
nav{
padding: 1.1em;
font-family: "Roboto Mono", monospace;
position: -webkit-sticky;
position: sticky;
top: 0; /* required */
transition: top 1s ease-in-out, border-radius 1s ease-in-out;
z-index: 5;
padding-left: 2em;
padding-right: 2em;
width: max-content;
background: var(--c-boxes);
/*background: -moz-linear-gradient(117deg, rgba(34,34,34,1) 0%, rgba(59,54,54,1) 100%);
background: -webkit-linear-gradient(117deg, rgba(34,34,34,1) 0%, rgba(59,54,54,1) 100%);
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;
text-align: center;
margin: auto;
border-radius: 2em;
}
nav.isSticky-nav{
border-top-left-radius: 0;
border-top-right-radius: 0;
}
nav ul #nav-logo{
border-right: 0.2em solid var(--c-lines);
}
/* Add class alias for logo used in TSX */
.logo {
border-right: 0.2em solid var(--c-lines);
}
nav ul #nav-logo span{
line-height: 0.75;
font-size: 1.5em;
}
nav a{
color: #fff;
transition: color 1s;
position: relative;
text-decoration: none;
}
nav a:hover{
color: #fff;
}
/* Unify link/summary layout to prevent distortion */
nav a,
nav summary {
color: #fff;
transition: color 1s;
position: relative;
text-decoration: none;
cursor: pointer;
display: inline-block; /* ensure consistent inline sizing */
vertical-align: middle; /* align with neighbors */
padding: 0; /* keep padding controlled by li */
}
nav a::before {
content: "";
position: absolute;
display: block;
width: 100%;
height: 2px;
bottom: 0;
left: 0;
background-color: #fff;
transform: scaleX(0);
transition: transform 0.3s ease;
}
nav a:hover::before {
transform: scaleX(1);
}
nav summary:hover {
color: #fff;
}
/* underline effect shared for links and summary */
nav a::before,
nav summary::before {
content: "";
position: absolute;
display: block;
width: 100%;
height: 2px;
bottom: 0;
left: 0;
background-color: #fff;
transform: scaleX(0);
transition: transform 0.3s ease;
}
nav a:hover::before,
nav summary:hover::before {
transform: scaleX(1);
}
/* Submenu support */
.hasSubmenu {
position: relative;
vertical-align: middle; /* align with other inline items */
}
/* Keep details inline to avoid breaking the first row flow */
.hasSubmenu details {
display: inline-block;
margin: 0;
padding: 0;
}
/* Ensure "Services" and caret stay on the same line */
.hasSubmenu details > summary {
display: inline-flex; /* horizontal layout */
align-items: center; /* vertical alignment */
gap: 0.5em; /* space between text and icon */
white-space: nowrap; /* prevent wrapping */
}
/* Hide native disclosure icon/marker on summary */
.hasSubmenu details > summary {
list-style: none;
outline: none;
}
.hasSubmenu details > summary::-webkit-details-marker {
display: none;
}
.hasSubmenu details > summary::marker {
content: "";
}
/* Reusable caret for submenu triggers */
.caret {
transition: transform 0.2s ease-in-out;
}
/* Rotate caret when submenu is open */
.hasSubmenu details[open] .caret {
transform: rotate(180deg);
}
/* Submenu box: place directly under nav with a tiny gap (no overlap) */
.submenu {
list-style: none;
margin: 1em 0;
padding: 0.5em 0;
position: absolute;
left: 0;
top: calc(100% + 0.25em);
display: none;
background: var(--c-background-light);
border: 1px solid var(--c-lines);
border-radius: 0.75em;
min-width: max-content;
text-align: left;
z-index: 10;
}
.submenu li {
display: block;
padding: 0;
}
.submenu a {
display: inline-block;
padding: 0; /* remove padding so underline equals text width */
margin: 0.35em 0; /* spacing without affecting underline width */
}
/* Show submenu when open */
.hasSubmenu details[open] .submenu {
display: flex;
flex-direction: column;
}
/* Hamburger toggle class (used by TSX) */
.toggle {
display: none;
transition: transform 0.5s ease;
}
.toggleRotated {
transform: rotate(180deg);
}
/* Bridge TSX classnames to existing rules */
.navList {
list-style: none;
padding: 0;
}
.navList li {
display: inline;
padding: 0 3em;
}
.navList li a {
text-decoration: none;
}
nav ul {
list-style: none;
padding: 0;
}
nav ul li {
display: inline;
padding: 0 3em;
}
nav ul li a {
text-decoration: none;
}
#toggle-nav{
display: none;
-webkit-transition: transform 0.5s ease;
-moz-transition: transform 0.5s ease;
-o-transition: transform 0.5s ease;
-ms-transition: transform 0.5s ease;
transition: transform 0.5s ease;
}
.toggle-nav-rotated {
transform: rotate(360deg);
}
.nav-open{
max-height: 20em;
}
@media only screen and (max-width: 990px){
#toggle-nav{
margin-top: 0.25em;
margin-left: 0.75em;
position: absolute;
left: 0;
display: block;
font-size: 2em;
}
nav{
width: 100%;
padding: 0;
border-top-right-radius: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 1em;
border-bottom-right-radius: 1em;
overflow: hidden;
}
nav ul {
margin-top: 1em;
gap: 2em;
display: flex;
flex-direction: column;
-webkit-transition: max-height 1s ease;
-moz-transition: max-height 1s ease;
-o-transition: max-height 1s ease;
-ms-transition: max-height 1s ease;
transition: max-height 1s ease;
max-height: 2em;
}
/* When TSX adds styles.open to the UL, expand it */
.open {
max-height: 20em;
}
nav ul:last-child{
padding-bottom: 1em;
}
nav ul #nav-logo {
margin: auto;
padding-bottom: 0.5em;
margin-bottom: -1em;
border-bottom: 0.2em solid var(--c-lines);
border-right: none;
}
/* Show hamburger on mobile */
.toggle {
margin-top: 0.25em;
margin-left: 0.75em;
position: absolute;
left: 0;
display: block;
font-size: 2em;
}
/* Submenu stacks inline under the parent item on mobile */
.submenu {
position: static;
border: none;
border-radius: 0;
background: transparent;
padding: 0 0 0.5em 0.5em;
min-width: unset;
}
.submenu a {
display: inline-block;
padding: 0; /* keep no padding on mobile too */
margin: 0.25em 0.5em; /* spacing via margin */
}
}

View File

@@ -1,52 +0,0 @@
import React, { useState, useContext } from "react"
import styles from "./HomeNav.module.css"
import { FaBars, FaChevronDown } from "react-icons/fa";
import { UserContext } from "../../context/UserContext";
export default function HomeNav() {
const [navOpen, setNavOpen] = useState(false)
const toggleNav = () => setNavOpen((prev) => !prev)
const { user } = useContext(UserContext);
return (
<nav className={styles.nav}>
<FaBars
className={`${styles.toggle} ${navOpen ? styles.toggleRotated : ""}`}
onClick={toggleNav}
aria-label="Toggle navigation"
aria-expanded={navOpen}
/>
<ul className={`${styles.navList} ${navOpen ? styles.open : ""}`}>
<li id="nav-logo" className={styles.logo}>
<span>vontor.cz</span>
</li>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="#portfolio">Portfolio</a>
</li>
<li className={styles.hasSubmenu}>
<details>
<summary>
Services
<FaChevronDown className={`${styles.caret} ml-2 inline-block`} aria-hidden="true" />
</summary>
<ul className={styles.submenu}>
<li><a href="#web">Web development</a></li>
<li><a href="#integration">Integrations</a></li>
<li><a href="#support">Support</a></li>
</ul>
</details>
</li>
<li>
<a href="#contactme-form">Contact me</a>
</li>
</ul>
</nav>
)
}