625d38645c
gates / gates (push) Successful in 5m54s
HEADER. logo.png carries its OWN "felhom.eu" lettering under the mark, so
putting it next to a typed <span class="logo-text"> said the name twice and
squeezed the mark down to 40px to make room. Now:
- the mark alone, bigger: assets/logo_notext.svg at 54px (44px under 600px).
Vector, so it stays crisp. Its two <text> elements are EMPTY, so R-916's
missing-font problem does not apply to this file -- checked, not assumed.
- the name beside it is the brand's OWN lettering, cropped from logo.png to
the new assets/logo-wordmark.png (632x108, tight bbox).
Why an image and not text: the wordmark's face is "M+ 2c"/"Vremena Grotesk"
(R-916). Nothing here has it, and the site deliberately loads NO external
font -- the privacy notice published this morning states exactly that, so
pulling in Google Fonts to match a logo would have made a published claim
false. Using the artwork is also what the Facebook covers already do. The
white/blue split the operator asked for is in the artwork itself.
Accessibility kept: the mark is alt="" aria-hidden (decorative), the wordmark
carries alt="felhom.eu", so the link still has its accessible name.
FOOTER. The legal links were unstyled, so the browser painted them default
blue and PURPLE once visited. They now follow the site's own convention
(.page-kapcsolat .sidebar-card a): --blue-bright, no underline, underline on
hover. :visited is pinned to the same colour deliberately -- a legal link
that changes colour after one read looks like it stopped working. The same
fix applied to .legal a on the two new legal pages, which had --blue instead
of --blue-bright.
18 of 18 pages carry the new lockup; the English pages keep their /en/ logo
href. site.css cache-bust v6 -> v7 on all 18, per the website rules. Verified
rendered in a browser on both a Hungarian and an English page: both images
load, no .logo-text left anywhere, footer links computed rgb(46,168,245).
site_gates OK (nav/footer per language, BOM, cache-busting, twins).
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=7">
|
|
<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_notext.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>
|