Refactor navbar and remove legacy API/context
Replaces HomeNav with a new SiteNav and associated CSS module, updating navigation structure and user menu. Removes legacy API client, downloader, user model, and UserContext in favor of a new AuthContext stub for future authentication logic. Also cleans up HeroCarousel and minor CSS fixes.
This commit is contained in:
@@ -103,4 +103,4 @@ footer .links a{
|
||||
padding-top: 1em;
|
||||
gap: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const videos = ["dQw4w9WgXcQ", "M7lc1UVf-VE", "aqz-KE-bpKQ"]; // placeholder IDs
|
||||
|
||||
export default function HeroCarousel() {
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setIndex(i => (i + 1) % videos.length), 10000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
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-[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-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">
|
||||
{/* Text */}
|
||||
<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-brand-text">Vontor.cz</span>
|
||||
</h1>
|
||||
<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-[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-brand-bgLight rounded-xl overflow-hidden shadow-2xl">
|
||||
{videos.map((v,i) => (
|
||||
<iframe
|
||||
key={v}
|
||||
src={`https://www.youtube.com/embed/${v}?autoplay=${i===index?1:0}&mute=1&loop=1&playlist=${v}`}
|
||||
title={`Slide ${i+1}`}
|
||||
className={`absolute inset-0 w-full h-full transition-opacity duration-700 ${i===index? 'opacity-100':'opacity-0'}`}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
))}
|
||||
<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-brand-accent':'bg-brand-lines/40 hover:bg-brand-lines/60'}`} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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: var(--c-text);
|
||||
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: var(--c-text);
|
||||
}
|
||||
/* 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: var(--c-other);
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
nav a:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
nav summary:hover {
|
||||
color: var(--c-text);
|
||||
}
|
||||
|
||||
/* 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: var(--c-other);
|
||||
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 */
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { useState } from "react"
|
||||
import styles from "./HomeNav.module.css"
|
||||
import { FaBars, FaChevronDown } from "react-icons/fa";
|
||||
|
||||
export default function HomeNav() {
|
||||
const [navOpen, setNavOpen] = useState(false)
|
||||
|
||||
const toggleNav = () => setNavOpen((prev) => !prev)
|
||||
|
||||
return (
|
||||
<nav className={styles.nav}>
|
||||
<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}>
|
||||
<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
|
||||
<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>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -1,73 +1,156 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { FaBars, FaTimes, FaChevronDown } from "react-icons/fa";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
FaUserCircle,
|
||||
FaSignOutAlt,
|
||||
FaSignInAlt,
|
||||
FaBars,
|
||||
FaChevronDown,
|
||||
FaGlobe,
|
||||
FaWrench,
|
||||
FaDownload,
|
||||
FaGitAlt,
|
||||
FaPlayCircle,
|
||||
FaUsers,
|
||||
FaHandsHelping,
|
||||
FaProjectDiagram,
|
||||
} from "react-icons/fa";
|
||||
import {FaClapperboard, FaCubes} from "react-icons/fa6";
|
||||
import styles from "./navbar.module.css";
|
||||
|
||||
/* Responsive sticky navigation bar using theme variables */
|
||||
export default function SiteNav() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [servicesOpen, setServicesOpen] = useState(false);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
export interface User {
|
||||
username: string;
|
||||
email?: string;
|
||||
avatarUrl?: string;
|
||||
}
|
||||
|
||||
interface NavbarProps {
|
||||
user: User | null;
|
||||
onLogin: () => void;
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
export default function Navbar({ user, onLogin, onLogout }: NavbarProps) {
|
||||
const [mobileMenu, setMobileMenu] = useState(false);
|
||||
const navRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
// close on outside click
|
||||
useEffect(() => {
|
||||
const onScroll = () => setScrolled(window.scrollY > 50);
|
||||
onScroll();
|
||||
window.addEventListener('scroll', onScroll);
|
||||
return () => window.removeEventListener('scroll', onScroll);
|
||||
function handleClick(e: MouseEvent) {
|
||||
if (!navRef.current) return;
|
||||
if (!navRef.current.contains(e.target as Node)) {
|
||||
// close only mobile menu here; dropdowns are CSS-controlled
|
||||
}
|
||||
}
|
||||
window.addEventListener("click", handleClick);
|
||||
return () => window.removeEventListener("click", handleClick);
|
||||
}, []);
|
||||
|
||||
// close dropdowns on Escape
|
||||
useEffect(() => {
|
||||
function onKey(e: KeyboardEvent) {
|
||||
if (e.key === "Escape") {
|
||||
setMobileMenu(false);
|
||||
}
|
||||
}
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<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>
|
||||
<nav className={styles.navbar} ref={navRef} aria-label="Hlavní navigace">
|
||||
{/* mobile burger */}
|
||||
<button
|
||||
className={styles.burger}
|
||||
onClick={() => setMobileMenu((p) => !p)}
|
||||
aria-expanded={mobileMenu}
|
||||
aria-label="Otevřít menu"
|
||||
>
|
||||
<FaBars />
|
||||
</button>
|
||||
|
||||
{/* left: brand */}
|
||||
<div className={styles.logo}>
|
||||
<a href="/" aria-label="vontor.cz home">vontor.cz</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* center links */}
|
||||
<div className={`${styles.links} ${mobileMenu ? styles.show : ""}`} role="menubar">
|
||||
{/* Services with submenu */}
|
||||
<div className={styles.dropdownItem}>
|
||||
<button
|
||||
className={styles.linkButton}
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<FaHandsHelping className={styles.iconSmall}/> Služby <FaChevronDown className={styles.chev} />
|
||||
</button>
|
||||
|
||||
<div className={styles.dropdown} role="menu" aria-label="Služby submenu">
|
||||
<a href="/services/web" role="menuitem"><FaGlobe className={styles.iconSmall}/> Weby</a>
|
||||
|
||||
{/* Filmařina as a simple link (no dropdown) */}
|
||||
<a href="/services/film" role="menuitem">
|
||||
<FaClapperboard className={styles.iconSmall}/> Filmařina
|
||||
</a>
|
||||
|
||||
|
||||
<a href="/services/drone-service" role="menuitem"><FaWrench className={styles.iconSmall}/> Servis dronu</a>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Toggle navigation"
|
||||
onClick={() => setOpen(o => !o)}
|
||||
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-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>
|
||||
<li className="flex"><NavLink to="/hosting-security" onClick={()=>setOpen(false)} className={linkCls}>Hosting & Security</NavLink></li>
|
||||
<li className="flex"><NavLink to="/donate" onClick={()=>setOpen(false)} className={linkCls}>Donate / Shop</NavLink></li>
|
||||
<li className="flex"><NavLink to="/contact" onClick={()=>setOpen(false)} className={linkCls}>Contact</NavLink></li>
|
||||
<li className="relative">
|
||||
|
||||
{/* Aplikace standalone submenu */}
|
||||
<div className={styles.dropdownItem}>
|
||||
<button className={styles.linkButton} aria-haspopup="true">
|
||||
<FaCubes className={styles.iconSmall}/> Aplikace <FaChevronDown className={styles.chev} />
|
||||
</button>
|
||||
<div className={styles.dropdown} role="menu" aria-label="Aplikace submenu">
|
||||
<a href="/apps/downloader" role="menuitem"><FaDownload className={styles.iconSmall}/> Downloader</a>
|
||||
<a href="/apps/git" role="menuitem"><FaGitAlt className={styles.iconSmall}/> Git</a>
|
||||
<a href="/apps/dema" role="menuitem"><FaPlayCircle className={styles.iconSmall}/> Dema</a>
|
||||
<a href="/apps/social" role="menuitem"><FaUsers className={styles.iconSmall}/> Social</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a className={styles.linkSimple} href="#contacts"><FaGlobe className={styles.iconSmall}/> Kontakt</a>
|
||||
<a className={styles.linkSimple} href="/projects"><FaProjectDiagram className={styles.iconSmall}/> Projekty</a>
|
||||
</div>
|
||||
|
||||
|
||||
{/*FIXME: STRČIT USER ČÁST DO LINK SKUPINY ABY TO BYLO KOMPATIBILNI PRO MOBIL*/}
|
||||
{/* right: user area */}
|
||||
<div className={styles.user}>
|
||||
{!user ? (
|
||||
<button className={styles.loginBtn} onClick={onLogin} aria-label="Přihlásit">
|
||||
<FaSignInAlt />
|
||||
</button>
|
||||
) : (
|
||||
<div className={`${styles.userWrapper} ${styles.dropdownItem}`}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setServicesOpen(s => !s)}
|
||||
className={`nav-item px-3 py-2 flex items-center gap-1`}
|
||||
aria-haspopup="true" aria-expanded={servicesOpen}
|
||||
className={styles.userButton}
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<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>
|
||||
{user.avatarUrl ? (
|
||||
<img src={user.avatarUrl} alt={`${user.username} avatar`} className={styles.avatar} />
|
||||
) : (
|
||||
<FaUserCircle className={styles.userIcon} />
|
||||
)}
|
||||
<span className={styles.username}>{user.username}</span>
|
||||
<FaChevronDown className={styles.chev}/>
|
||||
</button>
|
||||
{/* Mobile inline dropdown */}
|
||||
<div className={`md:hidden w-full mt-2 ${servicesOpen ? 'block' : 'hidden'}`}>
|
||||
<ul className="space-y-2 text-sm glass p-4">
|
||||
<li><a href="#live" className={`${dropdownCls} nav-item`}>Live Performance</a></li>
|
||||
<li><a href="#shop" className={`${dropdownCls} nav-item`}>Support Journey</a></li>
|
||||
<li><a href="#portfolio" className={`${dropdownCls} nav-item`}>Featured Work</a></li>
|
||||
</ul>
|
||||
|
||||
<div className={styles.dropdown} role="menu" aria-label="Uživatelské menu">
|
||||
<a href="/profile" role="menuitem">Profil</a>
|
||||
<a href="/billing" role="menuitem">Nastavení</a>
|
||||
<a href="/billing" role="menuitem">Platby</a>
|
||||
|
||||
<button className={styles.logoutBtn} onClick={onLogout} role="menuitem">
|
||||
<FaSignOutAlt /> Odhlásit se
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{/* Desktop offset dropdown anchored to right under nav */}
|
||||
{servicesOpen && (
|
||||
<div className="hidden md:block absolute top-full right-4 translate-y-2 min-w-56 glass p-4 shadow-xl">
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li><a href="#live" className={`${dropdownCls} nav-item`}>Live Performance</a></li>
|
||||
<li><a href="#shop" className={`${dropdownCls} nav-item`}>Support Journey</a></li>
|
||||
<li><a href="#portfolio" className={`${dropdownCls} nav-item`}>Featured Work</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
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%)]";
|
||||
447
frontend/src/components/navbar/navbar.module.css
Normal file
447
frontend/src/components/navbar/navbar.module.css
Normal file
@@ -0,0 +1,447 @@
|
||||
.navbar {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
padding: 0 2em;
|
||||
background-color: var(--c-boxes);
|
||||
color: white;
|
||||
font-family: "Roboto Mono", monospace;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
gap: 1rem;
|
||||
border-bottom-left-radius: 2em;
|
||||
border-bottom-right-radius: 2em;
|
||||
|
||||
--nav-margin-y: 1em;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
/* Brand */
|
||||
.logo {
|
||||
padding-right: 1em;
|
||||
border-right: 0.2em solid var(--c-lines);
|
||||
}
|
||||
|
||||
.logo a {
|
||||
font-size: 1.8em;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transition: text-shadow 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
.logo a:hover {
|
||||
text-shadow: 0.25em 0.25em 0.2em var(--c-text);
|
||||
}
|
||||
|
||||
/* Burger */
|
||||
.burger {
|
||||
display: none;
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.6em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Links container */
|
||||
.links {
|
||||
display: flex;
|
||||
gap: 1.6rem;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
width: -webkit-fill-available;
|
||||
}
|
||||
|
||||
/* Simple link */
|
||||
.linkSimple {
|
||||
color: var(--c-text);
|
||||
text-decoration: none;
|
||||
font-size: 1.05em;
|
||||
transition: transform 0.15s;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
/* TEXT SIZE UNIFICATION */
|
||||
.linkSimple,
|
||||
.user,
|
||||
.linkButton {
|
||||
font-size: 1.25em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dropdown a {
|
||||
font-size: 1.1em;
|
||||
color: var(--c-text);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.linkSimple:hover {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
/* Link item with dropdown */
|
||||
.linkItem {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Unified dropdown container */
|
||||
.dropdownItem {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.linkButton {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin: var(--nav-margin-y) auto;
|
||||
}
|
||||
|
||||
.linkButton:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* chevron icons */
|
||||
.chev {
|
||||
margin-left: 0.25rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.chevSmall {
|
||||
margin-left: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* dropdown */
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
top: auto;
|
||||
left: 0;
|
||||
width: -moz-max-content;
|
||||
width: max-content;
|
||||
background-color: var(--c-background-light);
|
||||
/* border: 1px solid var(--c-text); */
|
||||
padding: 0.6rem;
|
||||
/* border-radius: 0.45rem; */
|
||||
border-bottom-left-radius: 1em;
|
||||
border-bottom-right-radius: 1em;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
box-shadow: 0px 20px 24px 6px rgba(0, 0, 0, 0.35);
|
||||
z-index: 49;
|
||||
}
|
||||
|
||||
/* show dropdown on hover or keyboard focus within */
|
||||
.linkItem:hover .dropdown,
|
||||
.linkItem:focus-within .dropdown,
|
||||
.dropdownItem:hover .dropdown,
|
||||
.dropdownItem:focus-within .dropdown {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* nested wrapper for submenu items */
|
||||
.nestedWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* nested toggle (button that opens nested submenu) */
|
||||
.nestedToggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white !important;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nestedToggle:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
/* Unified dropdown toggle */
|
||||
.dropdownToggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white !important;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdownToggle:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
/* nested submenu */
|
||||
.nested {
|
||||
margin-top: 0.25rem;
|
||||
margin-left: 1.1rem;
|
||||
display: none;
|
||||
/* hidden until hover/focus within */
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
/* show nested submenu on hover/focus within */
|
||||
.nestedWrapper:hover .nested,
|
||||
.nestedWrapper:focus-within .nested {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Nested dropdown (dropdown inside dropdown) */
|
||||
.dropdown .dropdown {
|
||||
position: static;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding-left: 0.2rem;
|
||||
min-width: auto;
|
||||
margin-left: 1.1rem;
|
||||
}
|
||||
|
||||
/* links inside dropdown / nested */
|
||||
.dropdown a,
|
||||
.dropdown button {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.35rem 0.25rem;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: transform 0.12s;
|
||||
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropdown a:hover,
|
||||
.dropdown button:hover {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
/* small icons next to dropdown links */
|
||||
.iconSmall {
|
||||
margin-right: 0.45rem;
|
||||
font-size: 0.95rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* User area */
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
height: -webkit-fill-available;
|
||||
}
|
||||
|
||||
.loginBtn {
|
||||
width: max-content;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 1em;
|
||||
color: white;
|
||||
font-size: 0.98rem;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
|
||||
}
|
||||
.loginBtn svg {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.loginBtn:hover {
|
||||
background: var(--c-text);
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
/* user dropdown */
|
||||
.userWrapper {
|
||||
height: -webkit-fill-available;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.userWrapper .dropdown{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin-top: 3.5em;
|
||||
width: max-content;
|
||||
border-top-right-radius: 1em;
|
||||
}
|
||||
.userWrapper .dropdown a, button{
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.userButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: max-content;
|
||||
gap: 0.6rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.userIcon {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
max-width: max-content;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* logout button */
|
||||
.logoutBtn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Responsive: mobile */
|
||||
@media (max-width: 1010px) {
|
||||
.navbar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar .logo{
|
||||
border: none;
|
||||
}
|
||||
|
||||
.burger {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.burger svg {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.links {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
padding: 1rem 1.2rem;
|
||||
display: none;
|
||||
z-index: 40;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.03);
|
||||
|
||||
border-bottom-left-radius: 2em;
|
||||
border-bottom-right-radius: 2em;
|
||||
|
||||
transition: all 0.5s ease-in-out;
|
||||
max-height: 0;
|
||||
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
opacity: 0;
|
||||
|
||||
}
|
||||
.links.show {
|
||||
max-height: 100vh;
|
||||
padding: 1rem 1.2rem;
|
||||
background-color: var(--c-boxes);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
.linkButton{
|
||||
background-color: var(--c-background-light);
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
margin:auto;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1em;
|
||||
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.linkButton:hover{
|
||||
transform: none !important;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.linkSimple{
|
||||
margin: var(--nav-margin-y) auto;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding-left: 0.2rem;
|
||||
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
.dropdownItem{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nested {
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
|
||||
.dropdown .dropdown {
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
|
||||
.userButton .username{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user