fixed components

This commit is contained in:
2025-10-02 02:10:07 +02:00
parent 696d0e61f1
commit d0227e4539
26 changed files with 603 additions and 271 deletions

58
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,58 @@
# Copilot Instructions for Vontor CZ
## Overview
This monorepo contains a Django backend and a Vite/React frontend, orchestrated via Docker Compose. The project is designed for a Czech e-marketplace, with custom payment integrations and real-time features.
## Architecture
- **backend/**: Django project (`vontor_cz`), custom `account` app, and third-party payment integrations (`thirdparty/`).
- Uses Django REST Framework, Channels (ASGI), Celery, and S3/static/media via `django-storages`.
- Custom user model: `account.CustomUser`.
- API docs: DRF Spectacular (`/api/schema/`).
- **frontend/**: Vite + React + TypeScript app.
- Organized by `src/api/`, `components/`, `features/`, `layouts/`, `pages/`, `routes/`.
- Uses React Router layouts and nested routes (see `src/layouts/`, `src/routes/`).
- **docker-compose.yml**: Orchestrates backend, frontend, Redis, and Postgres for local/dev.
## Developer Workflows
- **Backend**
- Local dev: `python manage.py runserver` (or use Docker Compose)
- Migrations: `python manage.py makemigrations && python manage.py migrate`
- Celery: `celery -A vontor_cz worker -l info`
- Channels: Daphne/ASGI (see Docker Compose command)
- Env config: `.env` files in `backend/` (see `.gitignore` for secrets)
- **Frontend**
- Install: `npm install`
- Dev server: `npm run dev`
- Build: `npm run build`
- Preview: `npm run preview`
- Static assets: `src/assets/` (import in JS/CSS), `public/` (referenced in HTML)
## Conventions & Patterns
- **Backend**
- Use environment variables for secrets and config (see `settings.py`).
- Static/media files: S3 in production, local in dev (see `settings.py`).
- API versioning and docs: DRF Spectacular config in `settings.py`.
- Custom permissions, filters, and serializers in each app.
- **Frontend**
- Use React Router layouts for shared UI (see `src/layouts/`, `LAYOUTS.md`).
- API calls and JWT handling in `src/api/`.
- Route definitions and guards in `src/routes/` (`ROUTES.md`).
- Use TypeScript strict mode (see `tsconfig.*.json`).
- Linting: ESLint config in `eslint.config.js`.
## Integration Points
- **Payments**: `thirdparty/` contains custom integrations for Stripe, GoPay, Trading212.
- **Real-time**: Django Channels (ASGI, Redis) for websockets.
- **Task queue**: Celery + Redis for async/background jobs.
- **API**: REST endpoints, JWT auth, API key support.
## References
- [frontend/REACT.md](../frontend/REACT.md): Frontend structure, workflows, and conventions.
- [frontend/src/layouts/LAYOUTS.md](../frontend/src/layouts/LAYOUTS.md): Layout/component patterns.
- [frontend/src/routes/ROUTES.md](../frontend/src/routes/ROUTES.md): Routing conventions.
- [backend/vontor_cz/settings.py](../backend/vontor_cz/settings.py): All backend config, env, and integration details.
- [docker-compose.yml](../docker-compose.yml): Service orchestration and dev workflow.
---
**When in doubt, check the referenced markdown files and `settings.py` for project-specific logic and patterns.**

View File

@@ -3137,14 +3137,14 @@
} }
}, },
"node_modules/tinyglobby": { "node_modules/tinyglobby": {
"version": "0.2.14", "version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fdir": "^6.4.4", "fdir": "^6.5.0",
"picomatch": "^4.0.2" "picomatch": "^4.0.3"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=12.0.0"
@@ -3154,11 +3154,14 @@
} }
}, },
"node_modules/tinyglobby/node_modules/fdir": { "node_modules/tinyglobby/node_modules/fdir": {
"version": "6.4.6", "version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": { "peerDependencies": {
"picomatch": "^3 || ^4" "picomatch": "^3 || ^4"
}, },
@@ -3300,18 +3303,18 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "7.1.2", "version": "7.1.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz",
"integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.6", "fdir": "^6.5.0",
"picomatch": "^4.0.3", "picomatch": "^4.0.3",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"rollup": "^4.43.0", "rollup": "^4.43.0",
"tinyglobby": "^0.2.14" "tinyglobby": "^0.2.15"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"
@@ -3375,11 +3378,14 @@
} }
}, },
"node_modules/vite/node_modules/fdir": { "node_modules/vite/node_modules/fdir": {
"version": "6.4.6", "version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": { "peerDependencies": {
"picomatch": "^3 || ^4" "picomatch": "^3 || ^4"
}, },

View File

@@ -1,9 +1,18 @@
import { useState } from 'react' import { BrowserRouter as Router, Routes, Route, Link, Outlet } from "react-router-dom"
import './App.css' import Home from "./pages/home/home";
import HomeLayout from "./layouts/HomeLayout";
function App() { function App() {
return ( return (
/* */ <Router>
<Routes>
{/* Layout route */}
<Route path="/" element={<HomeLayout />}>
<Route index element={<Home />} />
</Route>
</Routes>
</Router>
) )
} }

View File

@@ -1,28 +0,0 @@
<footer id="contacts">
<div class="logo">
<h1>vontor.cz</h1>
</div>
<address>
Written by <b>David Bruno Vontor</b><br>
<p>Tel.: <a href="tel:+420 605 512 624"><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;"><u>21613109</u></a></p>
</address>
<div class="contacts">
<a href="https://github.com/Brunobrno">
<i class="fa fa-github"></i>
</a>
<a href="https://www.instagram.com/brunovontor/">
<i class="fa fa-instagram"></i>
</a>
<a href="https://twitter.com/BVontor">
<i class="fa-brands fa-x-twitter"></i>
</a>
<a href="https://steamcommunity.com/id/Brunobrno/">
<i class="fa-brands fa-steam"></i>
</a>
<a href="www.youtube.com/@brunovontor">
<i class="fa-brands fa-youtube"></i>
</a>
</div>
</footer>

View File

@@ -0,0 +1,36 @@
footer a{
color: var(--c-text);
text-decoration: none;
}
footer a i{
color: white;
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

@@ -0,0 +1,76 @@
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

@@ -0,0 +1,115 @@
import React, { useState } from "react"
import styles from "./contact-me.module.css"
interface ContactFormResponse {
success: boolean
[key: string]: any
}
export default function ContactMeForm() {
const [opened, setOpened] = useState(false)
const [formData, setFormData] = useState({ message: "", email: "" })
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState(false)
const [error, setError] = useState<string | null>(null)
const toggleOpen = () => {
setOpened((prev) => !prev)
}
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target
setFormData((prev) => ({ ...prev, [name]: value }))
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError(null)
try {
const response = await fetch("/submit-contactme/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": getCSRFToken(),
},
body: JSON.stringify(formData),
})
const data: ContactFormResponse = await response.json()
if (data.success) {
setSuccess(true)
setFormData({ message: "", email: "" })
} else {
setError("Zpráva nebyla odeslaná.")
}
} catch (err) {
setError("Chyba připojení nebo serveru.")
} finally {
setLoading(false)
}
}
// Utility to read CSRF token from cookies (Django)
const getCSRFToken = (): string => {
const match = document.cookie.match(/csrftoken=([^;]+)/)
return match ? match[1] : ""
}
return (
<div className={styles["contact-me"]}>
<div
className={
opened
? `${styles.opening} ${styles["rotate-opening"] || ""}`
: styles.opening
}
onClick={toggleOpen}
>
<i className="fa-solid fa-arrow-pointer" aria-hidden="true"></i>
</div>
<div
className={
opened
? `${styles.content} ${styles["content-moveup"] || ""}`
: styles.content
}
>
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
placeholder="Váš email"
value={formData.email}
onChange={handleChange}
required
/>
<textarea
name="message"
placeholder="Vaše zpráva"
value={formData.message}
onChange={handleChange}
required
/>
<input type="submit" value={loading ? "Odesílám..." : "Odeslat"} />
</form>
{success && (
<div className={styles.successFormAlert}>
<span>Zpráva odeslaná!</span>
<button onClick={() => setSuccess(false)}>×</button>
</div>
)}
{error && <div className={styles.errorFormAlert}>{error}</div>}
</div>
<div className={styles.cover}></div>
<div className={styles.triangle}></div>
</div>
)
}

View File

@@ -0,0 +1,36 @@
import React, { useState } from "react"
import styles from "./HomeNav.module.css"
export default function HomeNav() {
const [navOpen, setNavOpen] = useState(false)
const toggleNav = () => setNavOpen((prev) => !prev)
return (
<nav className={styles.nav}>
<i
id="toggle-nav"
className={`fa-solid fa-bars ${styles.toggle}`}
onClick={toggleNav}
></i>
<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>
<a href="#services">Services</a>
</li>
<li>
<a href="#contactme-form">Contact me</a>
</li>
</ul>
</nav>
)
}

View File

@@ -1,10 +0,0 @@
<nav>
<i id="toggle-nav" class="fa-solid fa-bars"></i>
<ul>
<li id="nav-logo"><span>vontor.cz</span></li>
<li><a href="{% url "home" %}">Home</a></li>
<li><a href="#portfolio">Portfolio</a></li>
<li><a href="#services">Services</a></li>
<li><a href="#contactme-form">Contact me</a></li>
</ul>
</nav>

View File

@@ -1,83 +0,0 @@
{% load static %}
<div class="drone only-desktop">
<video id="drone-video" class="video-background" autoplay muted loop playsinline>
<source id="video-source" type="video/mp4">
Your browser does not support video.
</video>
<article>
<header>
<h1>Letecké snímky dronem</h1>
</header>
<main>
<section>
<h2>Opravnění</h2>
A1, A2, A3 a průkaz na vysílačku!
Mohu garantovat bezpečný provoz dronu i ve složitějších podmínkách. Mám také možnost žádat o povolení k letu v blízkosti letišť!
</section>
<section>
<h2>Cena</h2>
Nabízím letecké záběry dronem <br>za cenu <u>3 000 </u>.
Pokud se nacházíte v Ostravě, doprava je zdarma. Pro oblasti mimo Ostravu účtuji 10 /km.
Cena se může odvíjet ještě podle složitosti získaní povolení.*
</section>
<section>
<h2>Výstup</h2>
Rád Vám připravím jednoduchý sestřih videa, který můžete rychle použít, nebo Vám mohu poskytnout samotné záběry k vlastní editaci. <br>
</section>
</main>
<div>
V případě zájmu neváhejte<br><a href="#contacts">kontaktovat!</a>
</div>
</article>
<script src="{% static 'home/js/drone.js' %}"></script>
</div>
<!--<button id="debug-drone">force reload</button>-->
drone.js:
$(document).ready(function () {
function setVideoDroneQuality() {
$sourceElement = $("#video-source");
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).width(); // Get screen width
// Determine the appropriate video source
if (screenWidth >= 1920) {
$sourceElement.attr('src', "https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.fullHD);
} else if (screenWidth >= 1280) {
$sourceElement.attr('src', "https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.hd);
} else {
$sourceElement.attr('src', "https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.lowRes);
}
// Reload the video
$('#drone-video')[0].load();
console.log("video set!");
}
setTimeout(1000);
setVideoDroneQuality();
//$("#debug-drone").click(setVideoDroneQuality);
});

View File

@@ -0,0 +1,108 @@
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é snímky dronem</h1>
</header>
<main>
<section>
<h2>Oprávnění</h2>
<p>
A1, A2, A3 a průkaz na vysílačku!
<br />
Mohu garantovat bezpečný provoz dronu i ve složitějších podmínkách.
Mám také možnost žádat o povolení k letu v blízkosti letišť!
</p>
</section>
<section>
<h2>Cena</h2>
<p>
Nabízím letecké záběry dronem <br />
za cenu <u>3 000 </u>.
</p>
<p>
Pokud se nacházíte v Ostravě, doprava je zdarma. Pro oblasti mimo Ostravu účtuji 10 /km.
</p>
<p>
Cena se může odvíjet ještě podle složitosti získaní povolení.*
</p>
</section>
<section>
<h2>Výstup</h2>
<p>
Rád Vám připravím jednoduchý sestřih videa, který můžete rychle použít,
nebo Vám mohu poskytnout samotné záběry k vlastní editaci.
</p>
</section>
</main>
<div>
V případě zájmu neváhejte <br />
<a href="#contacts">kontaktovat!</a>
</div>
</article>
</div>
)
}

View File

@@ -13,7 +13,7 @@
} }
.drone .video-background { .drone .videoBackground {
height: 100%; height: 100%;
width: 100%; width: 100%;
position: absolute; position: absolute;

View File

@@ -1,50 +0,0 @@
{% load static %}
<div class="portfolio" id="portfolio">
<header>
<h1>Portfolio</h1>
</header>
<div>
<span class="door"><i class="fa-solid fa-arrow-pointer"></i></span>
<article>
<header>
<a href="https://davo1.cz"><img src="{% static 'home\img\portfolio\DAVO_logo_2024_bile.png' %}" alt="davo1.cz logo"></a>
</header>
<main>
</main>
</article>
<article>
<header>
<a href="https://perlica.cz"><img src="{% static 'home\img\portfolio\perlica-3.webp' %}" alt="Perlica logo"></a>
</header>
<main>
</main>
</article>
<article>
<header>
<a href="http://epinger2.cz"><img src="{% static 'home\img\portfolio\logo_epinger.svg' %}" alt="Epinger2 logo"></a>
</header>
<main>
</main>
</article>
</div>
</div>
<script src="{% static 'home/js/portfolio.js' %}"></script>
portfolio.js:
$(document).ready(function () {
var doorOpen= false;
$(".door").click(function(){
doorOpen = !doorOpen;//převrátí hodnotu
if ($(".door").hasClass('door-open')){
$(".door").removeClass('door-open');
}else{
$(".door").addClass('door-open');
}
});
});

View File

@@ -54,10 +54,10 @@
.portfolio>header { .portfolio>header {
width: fit-content; width: fit-content;
position: absolute; position: absolute;
z-index: 5; z-index: 0;
top: -4.7em; top: -4.7em;
left: 0; left: 0;
padding: 1em 3em; padding: 0 3em;
padding-bottom: 0; padding-bottom: 0;
background-color: #cdc19c; background-color: #cdc19c;
color: #5e5747; color: #5e5747;
@@ -112,6 +112,7 @@
.portfolio div { .portfolio div {
width: 100%;
padding: 3em; padding: 3em;
background-color: #cdc19c; background-color: #cdc19c;
display: flex; display: flex;

View File

@@ -0,0 +1,65 @@
import React, { useState } from "react"
import styles from "./Portfolio.module.css"
interface PortfolioItem {
href: string
src: string
alt: string
}
const portfolioItems: PortfolioItem[] = [
{
href: "https://davo1.cz",
src: "/home/img/portfolio/DAVO_logo_2024_bile.png",
alt: "davo1.cz logo",
},
{
href: "https://perlica.cz",
src: "/home/img/portfolio/perlica-3.webp",
alt: "Perlica logo",
},
{
href: "http://epinger2.cz",
src: "/home/img/portfolio/logo_epinger.svg",
alt: "Epinger2 logo",
},
]
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}
>
<i className="fa-solid fa-arrow-pointer">fix missing font awesome</i>
</span>
{portfolioItems.map((item, index) => (
<article key={index} className={styles.article}>
<header>
<a href={item.href} target="_blank" rel="noopener noreferrer">
<img src={item.src} alt={item.alt} />
</a>
</header>
<main></main>
</article>
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,20 @@
import Footer from "../components/Footer/footer";
import ContactMeForm from "../components/Forms/ContactMe/ContactMeForm";
import HomeNav from "../components/navbar/HomeNav";
import Drone from "../features/ads/Drone/Drone";
import Portfolio from "../features/ads/Portfolio/Portfolio";
import Home from "../pages/home/home";
export default function HomeLayout(){
return(
<>
{/* Example usage of imported components, adjust as needed */}
<HomeNav />
<Home />
<ContactMeForm />
<Portfolio />
<Drone />
<Footer />
</>
)
}

View File

@@ -34,44 +34,9 @@ body{
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
} }
footer a{
color: var(--c-text);
text-decoration: none;
}
footer a i{
color: white;
text-decoration: none;
}
footer{
font-family: "Roboto Mono", monospace;
background-color: var(--c-boxes);
margin-top: 2em; .introduction {
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;
}
}
.introduction {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: var(--c-text); color: var(--c-text);
@@ -85,6 +50,7 @@ footer .contacts{
/* gap: 4em;*/ /* gap: 4em;*/
} }
.introduction h1{ .introduction h1{
font-size: 2em; font-size: 2em;
} }

View File

@@ -1,42 +0,0 @@
import React from "react";
import styles from "./Home.module.css";
export default function Home() {
return (
<div className={styles.container}>
<h1 className={styles.title}>Vítejte na hlavní stránce</h1>
<p>Toto je obsah jen pro home page.</p>
</div>
);
}
$(document).ready(function () {
$("body").click(function(event){
var randomId = "spark-" + Math.floor(Math.random() * 100000);
var $spark = $("<div>").addClass("spark-cursor").attr("id", randomId);
$("body").append($spark);
// Nastavení pozice
$spark.css({
"top": event.pageY + "px",
"left": event.pageX + "px",
"filter": "hue-rotate(" + Math.random() * 360 + "deg)"
});
for (let index = 0; index < 8; index++) {
let $span = $("<span>");
$span.css("transform", 'rotate(' + (index * 45) +"deg)" );
$spark.append($span);
}
setTimeout(() => {
$spark.find("span").addClass("animate");
}, 10);
setTimeout(function(){
$("#" + randomId).remove();
}, 1000);
});
});

View File

@@ -0,0 +1,49 @@
import React, { useEffect } from "react"
import styles from "./Home.module.css"
export default function Home() {
useEffect(() => {
const handleClick = (event: MouseEvent) => {
const randomId = "spark-" + Math.floor(Math.random() * 100000)
const spark = document.createElement("div")
spark.className = "spark-cursor"
spark.id = randomId
document.body.appendChild(spark)
// pozice a barva
spark.style.top = `${event.pageY}px`
spark.style.left = `${event.pageX}px`
spark.style.filter = `hue-rotate(${Math.random() * 360}deg)`
for (let i = 0; i < 8; i++) {
const span = document.createElement("span")
span.style.transform = `rotate(${i * 45}deg)`
spark.appendChild(span)
}
setTimeout(() => {
spark.querySelectorAll("span").forEach((s) => {
(s as HTMLElement).classList.add("animate")
})
}, 10)
setTimeout(() => {
spark.remove()
}, 1000)
}
document.body.addEventListener("click", handleClick)
// cleanup když komponenta zmizí
return () => {
document.body.removeEventListener("click", handleClick)
}
}, [])
return (
<></>
)
}