Files
felhom.eu/website/kapcsolat.html
T
admin 916d936332 website: restore the index grid background
Git archaeology: the grid was never a deliberate removal. It lived as a fixed
`body::before` in index.html's inline <style> block and was dropped when bed8675
("D3 Part 2: index + kapcsolat on design system v2") migrated the page onto
site.css. dd54e4c, which created site.css, has no body::before — it was a
porting omission, and nothing took its place. No asset was lost; it was pure CSS.

Restored at its original geometry and subtlety (50px cells, 1px lines, 3%), not
redesigned. Only change: the accent is the v2 --blue #0083D8 instead of the
retired legacy #0088cc, which site_gates.py bans. Scoped to body.page-index
because index is the only page that ever had it.

site.css cache-bust bumped to v=2 across all seven pages (nginx caches 7d).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-19 09:03:03 +02:00

598 lines
29 KiB
HTML

<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kapcsolat — Kérj személyre szabott ajánlatot otthoni szerveredhez | Felhom.eu</title>
<meta name="description" content="Írj nekünk és személyre szabott ajánlatot készítünk otthoni szerveredhez. Kérdésed van? Segítünk! Email: info@felhom.eu">
<meta name="keywords" content="kapcsolat, ajánlatkérés, otthoni szerver ajánlat, Felhom elérhetőség">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://felhom.eu/kapcsolat">
<meta property="og:type" content="website">
<meta property="og:url" content="https://felhom.eu/kapcsolat">
<meta property="og:title" content="Kapcsolat — Felhom.eu">
<meta property="og:description" content="Írj nekünk és személyre szabott ajánlatot készítünk otthoni szerveredhez.">
<meta property="og:image" content="https://felhom.eu/assets/og-image.png">
<meta property="og:locale" content="hu_HU">
<meta property="og:site_name" content="Felhom.eu">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Kapcsolat — Felhom.eu">
<meta name="twitter:description" content="Kérdésed van? Írj nekünk: info@felhom.eu">
<meta name="twitter:image" content="https://felhom.eu/assets/og-image.png">
<!-- shared assets: bump ?v= together on any site.css/icons.svg change -->
<link rel="stylesheet" href="/assets/site.css?v=2">
<link rel="icon" type="image/png" href="/assets/logo_favicon_2.svg">
<script defer src="https://stats.felhom.eu/script.js" data-website-id="d419db57-5e1c-48df-bdb3-c29f25fb1661"></script>
</head>
<body class="page-kapcsolat">
<nav>
<div class="container">
<a href="/" class="logo">
<img src="/assets/logo.png" alt="Felhom.eu logo">
<span class="logo-text">felhom.eu</span>
</a>
<ul class="nav-links">
<li><a href="/#szolgaltatasok">Szolgáltatások</a></li>
<li><a href="/alkalmazasok">Alkalmazások</a></li>
<li><a href="/technologiak">Technológiák</a></li>
<li><a href="/biztonsagimentes">Mentések</a></li>
<li><a href="/gyik">GYIK</a></li>
<li><a href="/kapcsolat" class="active">Kapcsolat</a></li>
</ul>
<button class="hamburger" id="hamburger" aria-label="Menü megnyitása" aria-expanded="false">
<span class="hamburger-box">
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
</span>
</button>
</div>
</nav>
<div class="mobile-overlay" id="mobileOverlay"></div>
<section class="page-header">
<div class="container">
<h1>Írj nekünk <span>bátran!</span></h1>
<p>Kérdésed van, vagy szeretnéd megbeszélni az igényeidet? Töltsd ki az űrlapot, és hamarosan válaszolunk.</p>
</div>
</section>
<main>
<section class="content-section" style="padding-top: 0;">
<div class="container">
<div class="contact-layout">
<!-- Form -->
<div class="contact-form-card">
<div id="formStatus" class="form-status"></div>
<form id="contactForm" novalidate>
<div style="position:absolute;left:-9999px;" aria-hidden="true">
<input type="text" name="website" tabindex="-1" autocomplete="off">
</div>
<div class="form-row">
<div class="form-group">
<label for="name">Név <span class="required">*</span></label>
<input type="text" id="name" name="name" placeholder="Teljes neved" required>
<div class="error-message" id="nameError">Kérlek, add meg a neved.</div>
</div>
<div class="form-group">
<label for="email">Email <span class="required">*</span></label>
<input type="email" id="email" name="email" placeholder="pelda@email.hu" required>
<div class="error-message" id="emailError">Kérlek, adj meg egy érvényes email címet.</div>
</div>
</div>
<div class="form-group">
<label for="subject">Tárgy <span class="required">*</span></label>
<select id="subject" name="subject" required>
<option value="" disabled selected>Válassz témát...</option>
<option value="zart-teszt">Zárt teszt — jelentkezem tesztelőnek</option>
<option value="erdeklodes">Érdeklődés — szeretném megismerni a szolgáltatást</option>
<option value="arajanlat">Árajánlat kérés — konkrét igényeim vannak</option>
<option value="tamogatas">Támogatás — meglévő szerveremet érintő kérdés</option>
<option value="egyeb">Egyéb</option>
</select>
<div class="error-message" id="subjectError">Kérlek, válassz egy témát.</div>
</div>
<div class="form-group">
<label for="message">Üzenet <span class="required">*</span></label>
<textarea id="message" name="message" placeholder="Írd le, miben segíthetünk..." required></textarea>
<div class="error-message" id="messageError">Kérlek, írd le az üzeneted.</div>
</div>
<div class="form-group">
<label>Fájl csatolás <span style="color: var(--text-3); font-weight: 400;">(opcionális)</span></label>
<div class="file-upload-zone" id="dropZone">
<input type="file" id="fileInput" multiple accept=".pdf,.doc,.docx,.xls,.xlsx,.png,.jpg,.jpeg,.gif,.webp,.txt,.csv,.zip,.7z">
<div class="upload-icon"><svg class="ico-tile" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-paperclip"/></svg></div>
<div class="upload-text">
<strong>Kattints ide</strong> vagy húzd ide a fájlokat
</div>
<div class="upload-hint">PDF, képek, dokumentumok · Max. 20 MB összesen</div>
</div>
<div class="file-list" id="fileList"></div>
<div class="file-total-size" id="fileTotalSize"></div>
</div>
<div class="checkbox-group">
<input type="checkbox" id="privacy" name="privacy" required>
<label for="privacy">
Elfogadom, hogy az űrlapon megadott adataimat a Felhom.eu a megkeresésem megválaszolásához felhasználja. Az adatokat harmadik félnek nem adjuk ki.
</label>
</div>
<div class="error-message" id="privacyError" style="margin-top: -16px; margin-bottom: 16px;">Az adatkezelési hozzájárulás szükséges.</div>
<button type="submit" class="submit-button" id="submitBtn">
<span class="btn-text">Üzenet küldése →</span>
<span class="spinner"></span>
</button>
</form>
</div>
<!-- Sidebar -->
<div class="contact-sidebar">
<div class="sidebar-card">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-mail"/></svg></span> Közvetlen email</h3>
<p>Ha inkább emailben írnál:</p>
<a href="mailto:info@felhom.eu" class="direct-email">
<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-mail"/></svg> info@felhom.eu
</a>
</div>
<div class="sidebar-card">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-clock"/></svg></span> Válaszidő</h3>
<p>Általában <strong style="color: var(--blue-bright);">24 órán belül</strong> válaszolunk munkanapokon.</p>
<p>Sürgős támogatási kérdés esetén jelezd a tárgyban.</p>
</div>
<div class="sidebar-card">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-lightbulb"/></svg></span> Miben segíthetünk?</h3>
<ul class="sidebar-list">
<li><span class="li-icon"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-monitor"/></svg></span> Szerver konfiguráció tanácsadás</li>
<li><span class="li-icon"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg></span> Alkalmazás telepítés és beállítás</li>
<li><span class="li-icon"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-lock"/></svg></span> Biztonsági mentés kialakítás</li>
<li><span class="li-icon"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-globe"/></svg></span> Távoli elérés beállítása</li>
<li><span class="li-icon"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-wrench"/></svg></span> Meglévő szerver karbantartás</li>
</ul>
</div>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<p>© 2026 felhom.eu — Saját felhőd, saját szabályaid</p>
</div>
</footer>
<script>
(() => {
'use strict';
// ==============================================
// CONFIGURATION
// ==============================================
const CONFIG = {
// TODO: Replace with your actual form backend endpoint.
// Options:
// 1. Self-hosted: Deploy a small container (e.g. Go/Python) that receives POST and sends via SMTP
// 2. Formspree: 'https://formspree.io/f/YOUR_FORM_ID'
// 3. Cloudflare Workers: Deploy a worker that forwards to email
//
// For now, falls back to mailto: if no endpoint is configured.
formEndpoint: '/api/contact',
maxTotalFileSize: 20 * 1024 * 1024, // 20 MB
maxSingleFileSize: 10 * 1024 * 1024, // 10 MB per file
maxFiles: 5,
allowedExtensions: ['pdf','doc','docx','xls','xlsx','png','jpg','jpeg','gif','webp','txt','csv','zip','7z'],
};
// ==============================================
// DOM REFS
// ==============================================
const form = document.getElementById('contactForm');
const submitBtn = document.getElementById('submitBtn');
const formStatus = document.getElementById('formStatus');
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const fileListEl = document.getElementById('fileList');
const fileTotalSizeEl = document.getElementById('fileTotalSize');
let attachedFiles = [];
// Preselect the subject from ?tema= (the „Zárt teszt" CTA on the landing page
// links here as /kapcsolat?tema=zart-teszt). Unknown values are ignored.
(function preselectSubject() {
const wanted = new URLSearchParams(window.location.search).get('tema');
if (!wanted) return;
const select = document.getElementById('subject');
if (!select) return;
const match = Array.from(select.options).some(o => o.value === wanted);
if (match) {
select.value = wanted;
} else {
console.warn('[kapcsolat] unknown ?tema= value, ignoring:', wanted);
}
})();
// ==============================================
// VALIDATION
// ==============================================
function validateField(field) {
const errorEl = document.getElementById(field.id + 'Error');
let valid = true;
if (field.type === 'checkbox') {
valid = field.checked;
} else if (field.required && !field.value.trim()) {
valid = false;
} else if (field.type === 'email' && field.value.trim()) {
valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(field.value.trim());
}
if (valid) {
field.classList.remove('error');
if (errorEl) errorEl.classList.remove('visible');
} else {
field.classList.add('error');
if (errorEl) errorEl.classList.add('visible');
}
return valid;
}
function validateForm() {
const fields = ['name', 'email', 'subject', 'message', 'privacy'];
let allValid = true;
fields.forEach(id => {
const field = document.getElementById(id);
if (!validateField(field)) allValid = false;
});
return allValid;
}
// Live validation on blur
['name', 'email', 'subject', 'message', 'privacy'].forEach(id => {
const el = document.getElementById(id);
const event = el.type === 'checkbox' ? 'change' : 'blur';
el.addEventListener(event, () => validateField(el));
// Clear error on input
el.addEventListener('input', () => {
if (el.classList.contains('error')) validateField(el);
});
});
// ==============================================
// FILE HANDLING
// ==============================================
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function getFileIcon(name) {
const ext = name.split('.').pop().toLowerCase();
const icons = {
pdf: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-file-text"/></svg>', doc: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-file-text"/></svg>', docx: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-file-text"/></svg>', xls: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-chart-column"/></svg>', xlsx: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-chart-column"/></svg>',
png: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-image"/></svg>', jpg: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-image"/></svg>', jpeg: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-image"/></svg>', gif: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-image"/></svg>', webp: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-image"/></svg>',
txt: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-file-text"/></svg>', csv: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-chart-column"/></svg>', zip: '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg>', '7z': '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg>',
};
return icons[ext] || '<svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-paperclip"/></svg>';
}
function getTotalFileSize() {
return attachedFiles.reduce((sum, f) => sum + f.size, 0);
}
function addFiles(fileArray) {
for (const file of fileArray) {
// Check extension
const ext = file.name.split('.').pop().toLowerCase();
if (!CONFIG.allowedExtensions.includes(ext)) {
showStatus(`A "${file.name}" fájltípus nem engedélyezett.`, 'error-state');
continue;
}
// Check single file size
if (file.size > CONFIG.maxSingleFileSize) {
showStatus(`A "${file.name}" túl nagy (max. ${formatFileSize(CONFIG.maxSingleFileSize)} / fájl).`, 'error-state');
continue;
}
// Check max file count
if (attachedFiles.length >= CONFIG.maxFiles) {
showStatus(`Maximum ${CONFIG.maxFiles} fájl csatolható.`, 'error-state');
break;
}
// Check total size
if (getTotalFileSize() + file.size > CONFIG.maxTotalFileSize) {
showStatus(`Összesített fájlméret meghaladja a ${formatFileSize(CONFIG.maxTotalFileSize)} limitet.`, 'error-state');
break;
}
// Check duplicate
if (attachedFiles.some(f => f.name === file.name && f.size === file.size)) {
continue;
}
attachedFiles.push(file);
}
renderFileList();
}
function removeFile(index) {
attachedFiles.splice(index, 1);
renderFileList();
// Reset file input so same file can be re-added
fileInput.value = '';
}
function renderFileList() {
if (attachedFiles.length === 0) {
fileListEl.innerHTML = '';
fileTotalSizeEl.textContent = '';
return;
}
fileListEl.innerHTML = attachedFiles.map((file, i) => `
<div class="file-item">
<span class="file-icon">${getFileIcon(file.name)}</span>
<div class="file-info">
<div class="file-name" title="${file.name}">${file.name}</div>
<div class="file-size">${formatFileSize(file.size)}</div>
</div>
<button type="button" class="file-remove" data-index="${i}" title="Eltávolítás">&#215;</button>
</div>
`).join('');
// Bind remove buttons
fileListEl.querySelectorAll('.file-remove').forEach(btn => {
btn.addEventListener('click', () => removeFile(parseInt(btn.dataset.index)));
});
// Total size indicator
const total = getTotalFileSize();
const pct = (total / CONFIG.maxTotalFileSize) * 100;
fileTotalSizeEl.textContent = `${formatFileSize(total)} / ${formatFileSize(CONFIG.maxTotalFileSize)}`;
fileTotalSizeEl.className = 'file-total-size';
if (pct > 90) fileTotalSizeEl.classList.add('error-text');
else if (pct > 70) fileTotalSizeEl.classList.add('warning');
}
// File input change
fileInput.addEventListener('change', () => {
if (fileInput.files.length) {
addFiles(Array.from(fileInput.files));
fileInput.value = '';
}
});
// Drag & drop
['dragenter', 'dragover'].forEach(evt => {
dropZone.addEventListener(evt, e => {
e.preventDefault();
dropZone.classList.add('dragover');
});
});
['dragleave', 'drop'].forEach(evt => {
dropZone.addEventListener(evt, e => {
e.preventDefault();
dropZone.classList.remove('dragover');
});
});
dropZone.addEventListener('drop', e => {
if (e.dataTransfer.files.length) {
addFiles(Array.from(e.dataTransfer.files));
}
});
// ==============================================
// STATUS MESSAGES
// ==============================================
function showStatus(message, type) {
formStatus.textContent = message;
formStatus.className = 'form-status ' + type;
formStatus.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
if (type === 'error-state') {
setTimeout(() => {
formStatus.className = 'form-status';
}, 6000);
}
}
// ==============================================
// FORM SUBMISSION
// ==============================================
form.addEventListener('submit', async (e) => {
e.preventDefault();
formStatus.className = 'form-status';
if (!validateForm()) {
// Build specific error message
const missing = [];
if (!document.getElementById('name').value.trim()) missing.push('név');
const emailVal = document.getElementById('email').value.trim();
if (!emailVal || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailVal)) missing.push('email');
if (!document.getElementById('subject').value) missing.push('tárgy');
if (!document.getElementById('message').value.trim()) missing.push('üzenet');
if (!document.getElementById('privacy').checked) missing.push('adatkezelési hozzájárulás');
if (missing.length === 1 && missing[0] === 'adatkezelési hozzájárulás') {
showStatus('Kérlek, fogadd el az adatkezelési hozzájárulást a küldéshez.', 'error-state');
} else {
showStatus('Kérlek, töltsd ki az összes kötelező mezőt.', 'error-state');
}
return;
}
submitBtn.classList.add('loading');
submitBtn.disabled = true;
const formData = {
name: document.getElementById('name').value.trim(),
email: document.getElementById('email').value.trim(),
subject: document.getElementById('subject').value,
message: document.getElementById('message').value.trim(),
};
// Subject label mapping
const subjectLabels = {
'zart-teszt': 'Jelentkezés — zárt teszt',
erdeklodes: 'Érdeklődés',
arajanlat: 'Árajánlat kérés',
tamogatas: 'Támogatás',
egyeb: 'Egyéb',
};
try {
if (CONFIG.formEndpoint) {
// --- Backend submission ---
const data = new FormData();
data.append('name', formData.name);
data.append('email', formData.email);
data.append('subject', subjectLabels[formData.subject] || formData.subject);
data.append('message', formData.message);
attachedFiles.forEach(file => {
data.append('attachments', file);
});
const response = await fetch(CONFIG.formEndpoint, {
method: 'POST',
body: data,
});
if (!response.ok) {
throw new Error(`Szerver hiba: ${response.status}`);
}
showStatus('Üzeneted sikeresen elküldtük! Hamarosan válaszolunk.', 'success');
form.reset();
attachedFiles = [];
renderFileList();
} else {
// --- Fallback: mailto ---
const subjectLine = `[Felhom.eu] ${subjectLabels[formData.subject] || formData.subject}`;
const body = [
`Név: ${formData.name}`,
`Email: ${formData.email}`,
``,
formData.message,
].join('\n');
const mailtoUrl = `mailto:info@felhom.eu?subject=${encodeURIComponent(subjectLine)}&body=${encodeURIComponent(body)}`;
window.location.href = mailtoUrl;
if (attachedFiles.length > 0) {
showStatus('Az email kliens megnyílt. A csatolt fájlokat kézzel kell hozzáadnod az emailhez, mert a mailto: link nem támogatja a mellékleteket.', 'error-state');
} else {
showStatus('Az email kliens megnyílt az üzeneteddel. Küld el az emailt a kapcsolatfelvételhez!', 'success');
}
}
} catch (err) {
console.error('Form submission error:', err);
showStatus(`Hiba történt a küldés során. Kérlek, próbáld újra vagy írj közvetlenül az info@felhom.eu címre.`, 'error-state');
} finally {
submitBtn.classList.remove('loading');
submitBtn.disabled = false;
}
});
// ==============================================
// DEBUG HELPER
// ==============================================
// Uncomment to test form states:
// window.debugForm = {
// showSuccess: () => showStatus('Üzeneted sikeresen elküldtük!', 'success'),
// showError: () => showStatus('Hiba történt.', 'error-state'),
// getFiles: () => console.log(attachedFiles),
// getFormData: () => {
// const fd = new FormData(form);
// for (const [k,v] of fd.entries()) console.log(k, v);
// },
// };
})();
</script>
<script>
(function() {
var hamburger = document.getElementById('hamburger');
var navLinks = document.querySelector('.nav-links');
var overlay = document.getElementById('mobileOverlay');
var body = document.body;
if (!hamburger || !navLinks || !overlay) {
console.warn('[mobile-menu] Missing elements:', {
hamburger: !!hamburger,
navLinks: !!navLinks,
overlay: !!overlay
});
return;
}
function openMenu() {
hamburger.classList.add('is-active');
navLinks.classList.add('is-open');
overlay.classList.add('is-visible');
body.classList.add('menu-open');
hamburger.setAttribute('aria-expanded', 'true');
hamburger.setAttribute('aria-label', 'Menü bezárása');
}
function closeMenu() {
hamburger.classList.remove('is-active');
navLinks.classList.remove('is-open');
overlay.classList.remove('is-visible');
body.classList.remove('menu-open');
hamburger.setAttribute('aria-expanded', 'false');
hamburger.setAttribute('aria-label', 'Menü megnyitása');
}
hamburger.addEventListener('click', function() {
if (navLinks.classList.contains('is-open')) {
closeMenu();
} else {
openMenu();
}
});
overlay.addEventListener('click', closeMenu);
navLinks.querySelectorAll('a').forEach(function(link) {
link.addEventListener('click', closeMenu);
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && navLinks.classList.contains('is-open')) {
closeMenu();
}
});
window.addEventListener('resize', function() {
if (window.innerWidth > 900 && navLinks.classList.contains('is-open')) {
closeMenu();
}
});
})();
</script>
</body>
</html>