templates/website/parts/footer.html.twig line 1

  1. <footer>
  2.     <div class="foot">
  3.         <div>
  4.             <b>Contact</b>
  5.             <p class="lead" style="margin:.4rem 0 0 0">vrshikyanstests@gmail.com</p>
  6.             <p class="lead" style="margin:.2rem 0 0 0">made by artyom19032008@gmail.com</p>
  7.         </div>
  8.         <div>
  9.             <b>Vrshikyans</b>
  10.             <p class="lead" style="margin:.4rem 0 0 0">Yerevan • Since 2025</p>
  11.             <small>© <span id="year"></span> Vrshikyans — All rights reserved.</small>
  12.         </div>
  13.     </div>
  14. </footer>
  15. <script>
  16.     // DATA: students as objects (edit this to add/remove students)
  17.         const STUDENT_CARDS = [
  18.             {
  19.                 name: 'Artyom H.',
  20.                 photo: 'students/Tyom.jpg',
  21.                 alt: 'Ani, Vrshikyans student with a high TOEFL score',
  22.                 score: 'SAT math 800/800',
  23.                 tests: 'SAT(Verbal --, Math 800)',
  24.                 blurb: 'Started with basic knoweledge in math and weak English, and after a year reached excellent scores in both SAT and TOEFL'
  25.             },
  26.             {
  27.                 name: 'Arman V.',
  28.                 photo: 'students/student_arman.jpg',
  29.                 alt: 'Arman, SAT and TOEFL student at Vrshikyans',
  30.                 score: 'SAT — 1520 / 1600',
  31.                 tests: 'Math 790 • R&W 730',
  32.                 blurb: 'Balances school, olympiads and prep. Uses the SAT & TOEFL tests here to simulate real exam mornings and track every improvement.'
  33.             },
  34.             {
  35.                 name: 'Maria K.',
  36.                 photo: 'students/student_maria.jpg',
  37.                 alt: 'Maria, IELTS student',
  38.                 score: 'IELTS Academic — 8.0',
  39.                 tests: 'L 8.5 • R 8.0 • W 7.5 • S 8.0',
  40.                 blurb: 'Practises reading and listening on the platform, then refines writing and speaking with targeted feedback from Ms Hasmik.'
  41.             },
  42.             {
  43.                 name: 'Davit S.',
  44.                 photo: 'students/student_davit.jpg',
  45.                 alt: 'Davit, ACT student',
  46.                 score: 'ACT — 33 / 36',
  47.                 tests: 'English • Reading • Science',
  48.                 blurb: 'Uses the calm exam-style layout to practise full ACT sets without distractions, focusing on timing and accuracy in Reading and Science.'
  49.             },
  50.             {
  51.                 name: 'Lilit M.',
  52.                 photo: 'students/student_lilit.jpg',
  53.                 alt: 'Lilit, TOEFL 2026 student',
  54.                 score: 'TOEFL 2026 — 27 Reading • 26 Listening',
  55.                 tests: 'Working towards 110+',
  56.                 blurb: 'Takes TOEFL 2026-style tests here every Sunday morning. Loves the smooth reading timer and automatic results page.'
  57.             },
  58.             {
  59.                 name: 'Narek A.',
  60.                 photo: 'students/student_narek.jpg',
  61.                 alt: 'Narek, future CS student',
  62.                 score: 'TOEFL — 108 / 120',
  63.                 tests: 'Planning for CS abroad',
  64.                 blurb: 'Uses the platform to practise reading long scientific passages and listening to lectures without pausing — just like on test day.'
  65.             }
  66.         ];
  67.     function renderStudentCards() {
  68.         const container = document.getElementById('studentCards');
  69.         if (!container) return;
  70.         container.innerHTML = STUDENT_CARDS.map(s => `
  71.                 <article class="student-card">
  72.                     <div class="avatar">
  73.                         <img src="${s.photo}" alt="${s.alt}"
  74.                              onerror="this.remove();this.parentElement.textContent='${s.name.split(' ')[0]}';">
  75.                     </div>
  76.                     <div class="meta">
  77.                         <h3 class="name">${s.name}</h3>
  78.                         <p class="score">${s.score}</p>
  79.                         ${s.tests ? `<p class="test-type">${s.tests}</p>` : ''}
  80.                         <p class="desc">${s.blurb}</p>
  81.                     </div>
  82.                 </article>
  83.             `).join('');
  84.     }
  85.     // Theme toggle
  86.     let isSwitching = false;
  87.     const validSections = ['home', 'mentors', 'students', 'value', 'exams', 'pricing'];
  88.     const themeBtn = document.getElementById('themeToggle');
  89.     const htmlEl = document.documentElement;
  90.     function setTheme(next) {
  91.         htmlEl.setAttribute('data-theme', next);
  92.         localStorage.setItem('theme', next);
  93.         themeBtn.textContent = next === 'light' ? '🌕' : '🌑';
  94.         themeBtn.setAttribute('aria-label', next === 'light' ? 'Switch to dark theme' : 'Switch to light theme');
  95.     }
  96.     themeBtn.addEventListener('click', () => setTheme(htmlEl.getAttribute('data-theme') === 'light' ? 'dark' : 'light'));
  97.     setTheme(htmlEl.getAttribute('data-theme') || 'dark');
  98.     // Section navigation
  99.     const navLinks = document.querySelectorAll('.nav-link, .mobile-nav-link');
  100.     const mobileMenu = document.getElementById('mobileMenu');
  101.     const mobileMenuBtn = document.getElementById('mobileMenuBtn');
  102.     function showSection(sectionId) {
  103.         if (!validSections.includes(sectionId) || isSwitching) return;
  104.         const newSection = document.getElementById(`${sectionId}-section`);
  105.         const oldSection = document.querySelector('.page-section.active');
  106.         if (!newSection || newSection === oldSection) return;
  107.         isSwitching = true;
  108.         document.body.classList.add('switching');
  109.         window.scrollTo({ top: 0, behavior: 'auto' });
  110.         if (oldSection) {
  111.             oldSection.classList.remove('active');
  112.             oldSection.classList.add('leaving');
  113.         }
  114.         newSection.classList.remove('leaving');
  115.         newSection.classList.add('active');
  116.         document.querySelectorAll('.nav-link, .mobile-nav-link').forEach(link => {
  117.             link.classList.toggle('active', link.dataset.section === sectionId);
  118.         });
  119.         if (mobileMenu) {
  120.             mobileMenu.classList.remove('active');
  121.         }
  122.         setTimeout(() => {
  123.             if (oldSection) {
  124.                 oldSection.classList.remove('leaving');
  125.             }
  126.             document.body.classList.remove('switching');
  127.             isSwitching = false;
  128.         }, 500);
  129.     }
  130.     navLinks.forEach(link => {
  131.         link.addEventListener('click', (e) => {
  132.             e.preventDefault();
  133.             const sectionId = link.getAttribute('data-section');
  134.             if (location.hash !== `#${sectionId}`) {
  135.                 location.hash = sectionId;
  136.             } else {
  137.                 showSection(sectionId);
  138.             }
  139.         });
  140.     });
  141.     function handleHashChange() {
  142.         const hash = (location.hash || '#home').replace('#', '');
  143.         const target = validSections.includes(hash) ? hash : 'home';
  144.         showSection(target);
  145.     }
  146.     window.addEventListener('hashchange', handleHashChange);
  147.     document.addEventListener('DOMContentLoaded', () => {
  148.         renderStudentCards();
  149.         handleHashChange();
  150.     });
  151.     // Mobile menu toggle
  152.     mobileMenuBtn.addEventListener('click', (e) => {
  153.         e.stopPropagation();
  154.         if (mobileMenu) {
  155.             mobileMenu.classList.toggle('active');
  156.         }
  157.     });
  158.     document.addEventListener('click', (e) => {
  159.         if (
  160.             mobileMenu &&
  161.             !mobileMenu.contains(e.target) &&
  162.             !mobileMenuBtn.contains(e.target)
  163.         ) {
  164.             mobileMenu.classList.remove('active');
  165.         }
  166.     });
  167.     // Scroll reveal
  168.     const io = new IntersectionObserver((entries) => {
  169.         entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } });
  170.     }, { threshold: .14 });
  171.     document.querySelectorAll('.reveal').forEach(el => io.observe(el));
  172.     // Subtle 3D tilt on the hero card
  173.     const heroCard = document.getElementById('heroCard');
  174.     if (heroCard) {
  175.         let af;
  176.         heroCard.addEventListener('mousemove', (e) => {
  177.             const r = heroCard.getBoundingClientRect();
  178.             const x = (e.clientX - r.left) / r.width - .5;
  179.             const y = (e.clientY - r.top) / r.height - .5;
  180.             const rx = (-y * 6).toFixed(2), ry = (x * 6).toFixed(2);
  181.             cancelAnimationFrame(af);
  182.             af = requestAnimationFrame(() => heroCard.style.transform = `translateY(-2px) rotateX(${rx}deg) rotateY(${ry}deg)`);
  183.         });
  184.         heroCard.addEventListener('mouseleave', () => heroCard.style.transform = 'translateY(0) rotateX(0) rotateY(0)');
  185.     }
  186.     const yearEl = document.getElementById('year');
  187.     if (yearEl) {
  188.         yearEl.textContent = new Date().getFullYear();
  189.     }
  190. </script>