43b67283c3
gates / gates (push) Failing after 14m58s
Two operator decisions from STOP 3, both independent of the home-page switch, which was NOT approved and has NOT happened. index.html still serves the old design; only the footer line and the button change on it. 1. The footer em dash, on all 21 page files (12 HU + 8 EN + the preview). "(c) 2026 felhom.eu - Sajat felhod..." now uses the spaced en dash, which is the correct Hungarian gondolatjel. Done as one sweep because site_gates.py gate 3 requires every page in a language set to carry an IDENTICAL footer, so this could never have been a one-page edit. Gate green: all 14 still match. Scope, stated honestly: this clears the footer only. 300 em dashes remain in the BODIES of the other 19 pages (gyik 53, en/faq 53, adatkezeles 33, szolgaltatasok-nonpublic 27, testing 24, teszteles 23, index 17/19, ...). The preview page is the only page at zero. The rest is NEXT.md work, page by page. 2. R-930, the operator's choice of the three offered: white on --blue measures 4.01:1, under WCAG AA's 4.5:1 for body text. .cta-button goes to 1.2rem/700, which is >=18.66px at >=700 weight, so WCAG counts it as LARGE text and the floor becomes 3:1 - which 4.01 clears without touching --blue. Darkening the token was rejected because --blue is shared with the hub and controller UIs. Horizontal padding trimmed 32->26px to protect the one-line rule for the longest label; verified in the browser at 390px, not assumed. .cta-button-secondary is unchanged: it already measures 7.17:1. This reaches the primary button on 16 pages, which is the point - the defect was never the home page's. site.css?v= bumped to 12 on all 21 files. The pages sat at v=10 and the preview at v=11; leaving them split would have served a returning visitor the old stylesheet and the change would have looked like it did not deploy. site_gates.py green.
603 lines
30 KiB
HTML
603 lines
30 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Contact — ask for a personal offer for your home server | Felhom.eu</title>
|
||
<meta name="description" content="Write to us and we will make a personal offer for your home server in Hungary. A question? We help! E-mail: info@felhom.eu">
|
||
|
||
<meta name="keywords" content="contact, request an offer, home server offer, Felhom contact">
|
||
<meta name="robots" content="index, follow">
|
||
<link rel="canonical" href="https://felhom.eu/en/contact">
|
||
<link rel="alternate" hreflang="hu" href="https://felhom.eu/kapcsolat">
|
||
<link rel="alternate" hreflang="en" href="https://felhom.eu/en/contact">
|
||
<link rel="alternate" hreflang="x-default" href="https://felhom.eu/kapcsolat">
|
||
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:url" content="https://felhom.eu/en/contact">
|
||
<meta property="og:title" content="Contact — Felhom.eu">
|
||
<meta property="og:description" content="Write to us and we will make a personal offer for your home server.">
|
||
<meta property="og:image" content="https://felhom.eu/assets/og-image-en.png">
|
||
<meta property="og:locale" content="en_GB">
|
||
<meta property="og:site_name" content="Felhom.eu">
|
||
|
||
<meta name="twitter:card" content="summary">
|
||
<meta name="twitter:title" content="Contact — Felhom.eu">
|
||
<meta name="twitter:description" content="A question? Write to us: info@felhom.eu">
|
||
<meta name="twitter:image" content="https://felhom.eu/assets/og-image-en.png">
|
||
<!-- shared assets: bump ?v= together on any site.css/icons.svg change -->
|
||
<link rel="stylesheet" href="/assets/site.css?v=12">
|
||
<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="/en/" class="logo">
|
||
<img class="logo-mark" src="/assets/logo-mark.svg" alt="" aria-hidden="true">
|
||
<img class="logo-word" src="/assets/logo-wordmark.png" alt="felhom.eu">
|
||
</a>
|
||
<ul class="nav-links">
|
||
<li><a href="/en/#services">Services</a></li>
|
||
<li><a href="/en/apps">Apps</a></li>
|
||
<li><a href="/en/technology">Technology</a></li>
|
||
<li><a href="/en/backups">Backups</a></li>
|
||
<li><a href="/en/faq">FAQ</a></li>
|
||
<li><a href="/en/contact" class="active">Contact</a></li>
|
||
<li class="lang-globe-li"><details class="lang-globe"><summary class="lang-globe-btn" aria-label="Language" title="Language"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-globe"/></svg></summary><ul class="lang-globe-menu"><li><a href="/kapcsolat" lang="hu" hreflang="hu" class="lang-globe-item">Magyar</a></li><li><a href="/en/contact" lang="en" hreflang="en" class="lang-globe-item current" aria-current="true">English</a></li></ul></details></li>
|
||
</ul>
|
||
<button class="hamburger" id="hamburger" aria-label="Open menu" 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>Write to <span>us!</span></h1>
|
||
<p>A question, or want to talk about what you need? Fill in the form and we will answer soon. We answer in English or Hungarian.</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">Name <span class="required">*</span></label>
|
||
<input type="text" id="name" name="name" placeholder="Your full name" required>
|
||
<div class="error-message" id="nameError">Enter your name.</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="email">E-mail <span class="required">*</span></label>
|
||
<input type="email" id="email" name="email" placeholder="you@example.com" required>
|
||
<div class="error-message" id="emailError">Enter a valid e-mail address.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="subject">Subject <span class="required">*</span></label>
|
||
<select id="subject" name="subject" required>
|
||
<option value="" disabled selected>Choose a topic...</option>
|
||
<option value="zart-teszt">Closed test — I want to be a tester</option>
|
||
<option value="erdeklodes">Interest — I would like to learn about the service</option>
|
||
<option value="arajanlat">Request a quote — I have specific needs</option>
|
||
<option value="tamogatas">Support — a question about my existing server</option>
|
||
<option value="egyeb">Other</option>
|
||
</select>
|
||
<div class="error-message" id="subjectError">Choose a topic.</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="message">Message <span class="required">*</span></label>
|
||
<textarea id="message" name="message" placeholder="Tell us how we can help..." required></textarea>
|
||
<div class="error-message" id="messageError">Write your message.</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Attach files <span style="color: var(--text-3); font-weight: 400;">(optional)</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>Click here</strong> or drag your files here
|
||
</div>
|
||
<div class="upload-hint">PDF, images, documents · max. 20 MB in total</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">
|
||
I have read the <a href="/adatkezeles" target="_blank" rel="noopener">privacy notice (in Hungarian)</a> and agree that my name, e-mail address, message and attached files are used to answer my enquiry. The message is carried by mail-sending and mailbox providers. I can withdraw this consent at any time at info@felhom.eu.
|
||
</label>
|
||
</div>
|
||
<div class="error-message" id="privacyError" style="margin-top: -16px; margin-bottom: 16px;">Your consent to the use of your details is needed.</div>
|
||
|
||
<button type="submit" class="submit-button" id="submitBtn">
|
||
<span class="btn-text">Send message →</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> E-mail us directly</h3>
|
||
<p>If you would rather send an e-mail:</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> Reply time</h3>
|
||
<p>We usually answer <strong style="color: var(--blue-bright);">within 24 hours</strong> on working days.</p>
|
||
<p>If a support question is urgent, say so in the subject.</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> How can we help?</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> Advice on a server set-up</li>
|
||
<li><span class="li-icon"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg></span> Installing and setting up apps</li>
|
||
<li><span class="li-icon"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-lock"/></svg></span> Setting up backups</li>
|
||
<li><span class="li-icon"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-globe"/></svg></span> Setting up remote access</li>
|
||
<li><span class="li-icon"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-wrench"/></svg></span> Maintaining an existing server</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<footer>
|
||
<div class="container">
|
||
<p>© 2026 felhom.eu – Your own cloud, your own rules. Felhom serves households in Hungary.</p>
|
||
<p class="footer-legal">Operated by Nagyfenyvesi Viktor · <a href="mailto:info@felhom.eu">info@felhom.eu</a> · <a href="/adatkezeles">Privacy notice (Hungarian)</a> · <a href="/feltetelek">Terms (Hungarian)</a></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 closed-test CTA on the landing page
|
||
// links here as /en/contact?tema=zart-teszt — the same values as the Hungarian page). 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('[contact] 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(`The file type of "${file.name}" is not allowed.`, 'error-state');
|
||
continue;
|
||
}
|
||
|
||
// Check single file size
|
||
if (file.size > CONFIG.maxSingleFileSize) {
|
||
showStatus(`"${file.name}" is too large (max. ${formatFileSize(CONFIG.maxSingleFileSize)} per file).`, 'error-state');
|
||
continue;
|
||
}
|
||
|
||
// Check max file count
|
||
if (attachedFiles.length >= CONFIG.maxFiles) {
|
||
showStatus(`You can attach at most ${CONFIG.maxFiles} files.`, 'error-state');
|
||
break;
|
||
}
|
||
|
||
// Check total size
|
||
if (getTotalFileSize() + file.size > CONFIG.maxTotalFileSize) {
|
||
showStatus(`The files together are over the ${formatFileSize(CONFIG.maxTotalFileSize)} limit.`, '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="Remove">×</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('name');
|
||
const emailVal = document.getElementById('email').value.trim();
|
||
if (!emailVal || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailVal)) missing.push('email');
|
||
if (!document.getElementById('subject').value) missing.push('subject');
|
||
if (!document.getElementById('message').value.trim()) missing.push('message');
|
||
if (!document.getElementById('privacy').checked) missing.push('consent');
|
||
|
||
if (missing.length === 1 && missing[0] === 'consent') {
|
||
showStatus('Tick the consent box to send the form.', 'error-state');
|
||
} else {
|
||
showStatus('Fill in every required field.', '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': 'Application — closed test (EN)',
|
||
erdeklodes: 'Interest (EN)',
|
||
arajanlat: 'Quote request (EN)',
|
||
tamogatas: 'Support (EN)',
|
||
egyeb: 'Other (EN)',
|
||
};
|
||
|
||
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(`Server error: ${response.status}`);
|
||
}
|
||
|
||
showStatus('Your message has been sent. We will answer soon.', 'success');
|
||
form.reset();
|
||
attachedFiles = [];
|
||
renderFileList();
|
||
|
||
} else {
|
||
// --- Fallback: mailto ---
|
||
const subjectLine = `[Felhom.eu] ${subjectLabels[formData.subject] || formData.subject}`;
|
||
const body = [
|
||
`Name: ${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('Your e-mail program has opened. Add the attached files to the e-mail by hand: a mailto: link cannot carry attachments.', 'error-state');
|
||
} else {
|
||
showStatus('Your e-mail program has opened with your message. Send the e-mail to get in touch.', 'success');
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('Form submission error:', err);
|
||
showStatus(`Something went wrong while sending. Try again, or write to info@felhom.eu directly.`, 'error-state');
|
||
} finally {
|
||
submitBtn.classList.remove('loading');
|
||
submitBtn.disabled = false;
|
||
}
|
||
});
|
||
|
||
// ==============================================
|
||
// DEBUG HELPER
|
||
// ==============================================
|
||
// Uncomment to test form states:
|
||
// window.debugForm = {
|
||
// showSuccess: () => showStatus('Your message has been sent.', 'success'),
|
||
// showError: () => showStatus('Something went wrong.', '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', 'Close menu');
|
||
}
|
||
|
||
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', 'Open menu');
|
||
}
|
||
|
||
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>
|