feat: add landing page templates and registration modal

- Implemented header and hero section in _header.html.twig and _hero_art.html.twig.
- Created a pre-registration modal in _reg_modal.html.twig with form fields and validation.
- Added page scripts for dynamic behavior and interaction in _page_scripts.html.twig.
- Developed landing page structure in landing.html.twig, integrating header, footer, and modal.
- Introduced tests for landing page rendering and registry validation in LandingPageTest.php and LandingRegistryTest.php.
This commit is contained in:
hamed
2026-08-10 14:38:27 +03:30
parent 231ce793bc
commit 68b8b05630
16 changed files with 1676 additions and 596 deletions
+166
View File
@@ -0,0 +1,166 @@
<script>
(function () {
'use strict';
var FA = [
'۰',
'۱',
'۲',
'۳',
'۴',
'۵',
'۶',
'۷',
'۸',
'۹'
];
function groupFa(n) {
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, '،').replace(/\d/g, function (d) {
return FA[+ d];
});
}
var header = document.getElementById('header');
function onScroll() {
header.classList.toggle('scrolled', window.scrollY > 12);
}
window.addEventListener('scroll', onScroll, {passive: true});
onScroll();
var navToggle = document.getElementById('navToggle');
var navLinks = document.getElementById('navLinks');
if (navToggle) {
navToggle.addEventListener('click', function () {
navLinks.classList.toggle('open');
});
navLinks.addEventListener('click', function (e) {
if (e.target.tagName === 'A')
navLinks.classList.remove('open');
});
}
var sections = [
'hero',
'features',
'why',
'download',
'contact'
];
var links = Array.prototype.slice.call(document.querySelectorAll('.nav-links a'));
function syncActive() {
var y = window.scrollY + 120,
cur = 'hero';
sections.forEach(function (id) {
var el = document.getElementById(id);
if (el && el.offsetTop <= y)
cur = id;
});
links.forEach(function (a) {
a.classList.toggle('active', a.getAttribute('href') === '#' + cur);
});
}
window.addEventListener('scroll', syncActive, {passive: true});
syncActive();
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var reveals = Array.prototype.slice.call(document.querySelectorAll('.reveal'));
function inView(el) {
var r = el.getBoundingClientRect();
return r.top<(window.innerHeight||document.documentElement.clientHeight)-30 && r.bottom> 0;
}
function reveal(el) {
el.classList.add('in');
}
if (reduce) {
reveals.forEach(reveal);
} else {
reveals.forEach(function (el) {
if (inView(el))
reveal(el);
});
if ('IntersectionObserver' in window) {
var io = new IntersectionObserver(function (es) {
es.forEach(function (e) {
if (e.isIntersecting) {
reveal(e.target);
io.unobserve(e.target);
}
});
}, {
threshold: 0.12,
rootMargin: '0px 0px -40px 0px'
});
reveals.forEach(function (el) {
if (! el.classList.contains('in'))
io.observe(el);
});
}
var ticking = false;
function sweep() {
if (ticking)
return;
ticking = true;
requestAnimationFrame(function () {
reveals.forEach(function (el) {
if (inView(el))
reveal(el);
});
ticking = false;
});
}
window.addEventListener('scroll', sweep, {passive: true});
window.addEventListener('resize', sweep, {passive: true});
setTimeout(function () {
reveals.forEach(reveal);
}, 2800);
}
// نماد اعتماد الکترونیکی (Enamad) — فقط بعد از لود کامل صفحه تزریق می‌شود تا
// درخواست تصویرِ دامنه‌ی بیرونی با منابع خود سایت رقابت نکند و سرعت لود پایین نیاید.
function mountEnamad() {
var slot = document.getElementById('enamad-seal');
if (! slot)
return;
var a = document.createElement('a');
a.setAttribute('referrerpolicy', 'origin');
a.target = '_blank';
a.href = 'https://trustseal.enamad.ir/?id=755841&Code=8sNSk0yrUE7EgbY9k5xkqTE0LaeXIaq4';
var img = document.createElement('img');
img.setAttribute('referrerpolicy', 'origin');
img.src = 'https://trustseal.enamad.ir/logo.aspx?id=755841&Code=8sNSk0yrUE7EgbY9k5xkqTE0LaeXIaq4';
img.alt = 'نماد اعتماد الکترونیکی';
img.style.cursor = 'pointer';
img.setAttribute('code', '8sNSk0yrUE7EgbY9k5xkqTE0LaeXIaq4');
img.loading = 'lazy';
a.appendChild(img);
slot.appendChild(a);
}
if (document.readyState === 'complete') {
mountEnamad();
} else {
window.addEventListener('load', mountEnamad);
}
})();
</script>